Raam Dev’s Weblog

Avatar

A man without scars has not lived his life to the fullest.

SSH Client Keys

SSH Client Keys allow you to quickly login to a remote server via SSH without typing your password. This is very useful if you login to a remote *nix server on a regular basis or if you want to automate scripts that need to remotely connect using SSH (using commands such as rsync or cvs over SSH).

I have used SSH keys for awhile now, but whenever I setup a new server I seem to draw a blank when trying to remember how to set them up. Each time I end up searching Google for “SSH Client Keys” and clicking on the excellent O’Reilly page “Quick Logins with ssh Client Keys“. I really don’t like duplicating information that is already available on the web, but I felt it was necessary to explain a couple of points the O’Reilly page misses, particularly about the authorized_keys2 file on the server.

Because I’ve followed the setup procedure so many times, I usually only need to glance at the directions to remember how its done. However, it was doing this that caused me much frustration today. I discovered that it is very important that the server-side ~/.ssh directory (and all files inside) are chmod 0700(!), otherwise this whole process is pointless!

Before I review how to setup SSH Client Keys, let me give a brief overview of the files involved:

Client-side:
~/.ssh/id_rsa (private key, chmod 0600)
~/.ssh/id_rsa.pub (public key, chmod 0655)

Server-side:
~/.ssh/authorized_keys2 (holds a list of public keys, chmod 0700)

Now that you know what files are needed, let me explain how to go about creating them. The procedure for getting SSH keys setup is rather straightforward. First of all, if you’ve never used SSH keys before you probably need to generate a public/private key pair on the client-side (your workstation). From your home directory, run the following command:

$ ssh-keygen -t rsa

When prompted, leave the default options as they are (that includes leaving the passphrase option blank) and simply press Enter until you’re back at your command prompt. If you did not already have a ~/.ssh directory, this command will create the directory and place two files inside: Your private id_rsa and the public id_rsa.pub version of it to use on remote servers.

Now that you have the Client-side files you need, it’s time to create the necessary server-side files and copy the contents of your public key file (id_rsa.pub) to authorized_keys2 on the server-side. The procedure outlined on the O’Reilly page assumes you don’t already have any SSH keys setup on the remote server and simply replaces authorized_keys2 with the contents of id_rsa.pub. The two commands you are instructed to run are:

$ ssh server "mkdir .ssh; chmod 0700 .ssh"
$ scp .ssh/id_rsa.pub server:.ssh/authorized_keys2

While this is fine for those who are setting the keys up for the first time on a new server/account, it may slip up those who already use them. If the file already exists, it will overwrite any existing keys listed in the authorized_keys2 file. The authorized_keys2 file is simply a text file list of the public keys (the contents of ~/.ssh/id_rsa.pub on the client-side).The easiest thing to do if the file already exists on the server-side is to simply copy the contents of your ~/.ssh/id_rsa.pub file, SSH over to the server, open authorized_keys2, and paste your key at the bottom of the list.

Now you should be able to type ssh server and automatically login without typing a password!

Using wget to run a PHP script

wget is usually used to download a file or web page via HTTP, so by default running the wget http://www.example.com/myscript.php would simply create a local file called myscript.php and it would contain the contents of the script output. But I don’t want that — I want to execute the script and optionally redirect its output somewhere else (to a log file or into an email for reporting purposes). So here is how it’s done:

$ wget -O - -q http://www.example.com/myscript.php >> log.txt

According to the wget man page, the “-O -” option is used to prevent wget from saving the file locally and instead simply outputs the result of the request. Also, wget normally produces it’s own output (a progress bar showing the status of the download and some other verbose information) but we don’t care about that stuff so we turn it off with the “-q” option. Lastly, the “>> log.txt” redirects the output of the script to a local file called log.txt. This could also be a pipe command to send the output as an email.

There is an incredible amount of power behind wget and there are a lot of cool things you can use it for besides calling PHP scripts from the command line. Check out this LifeHacker article for a bunch of cool uses.

Recursively Renaming Files - The One Liner

A couple of months ago I wrote about a solution to recursively rename multiple files on a Linux system. The problem with that solution was that the script needed to be saved as a file called rename and then chmod 755 to make it executable.

Today, while writing a script for my ASAP application, I found a much easier one line solution which uses commonly installed command line tools:

$ for i in `find . -name "*.php5"` ; do mv -v $i ${i/\.php/\.php5/}; done

This chain of commands searches for all files containing .php5 and renames them to .php. The most obvious limitation of this solution is that if a filename or directory contains .php5, it will also be renamed. So if, for some wacky reason, you had a directory called /my.php5.files/, that directory would be renamed to /my.php.files/. Similarly, a file named my.php5.example.php would be renamed to my.php.example.php.

For my application, this one liner worked fine as I simply added a warning to the top of my script. If anyone knows how I can easily modify that command to ignore all directories (I didn’t see anything in the find command syntax that might help), I would greatly appreciate the information!

Moving a CVS 1.12 repository to CVS 1.11

Over the weekend I moved my entire CVS repository from my home server to a domain hosted on the dedicated web server for my web hosting company. From what I read online, it was as simple as copying all the files to the new location. This appeared to work, but when I tried to create a new project and share it to the repository using Eclipse, I received the following error:

CVS Error

Hmm, unrecognized keyword 'UseNewInfoFmtStrings'. I tried searching Google and although I didn’t find very much in the way of a solution, there were hints to the error possibly relating to differences in CVS versions. So I checked my home server version: CVS 1.12.13. Then I checked the version of CVS on my web server: CVS 1.11.17. Ah ha!

My next thought was to upgrade CVS on my web server. But then I discovered my web server (CentOS 4) doesn’t have the CVS 1.12 package available because only stable packages are supported. I decided it was best to keep the server stable and starting looking for a way to “downgrade” the 1.12 repository to make it compatible with 1.11.

My eventual solution was to backup /home/dev82/cvsroot/CVSROOT/history, which contains the history information for all the files in the repository, and then delete the /home/dev82/cvsroot/CVSROOT/ directory. After this, I simply ran the following command to recreate CVSROOT and make it compatible with CVS 1.11:

root@web# cvs -d /home/dev82/cvsroot/ init

With the fresh CVSROOT directory in place, I copied the history file back to /home/dev82/cvsroot/CVSROOT/, overwriting the existing one that the cvs init command created.

My biggest worry with doing this was possibly losing history information or somehow being unable to restore files, etc. However, I was able to successfully, create, commit, and restore files from the history. I also had no errors creating new projects and sharing them with the CVS repository.

If anyone knows of a better solution, or has any information on what potential problems following this procedure might have, please leave a comment and let me know!

IMPORTANT UPDATE: I discovered any new files I committed to my repository were being saved to the ./Attic directory. The ./Attic directory is used by CVS for files that have been deleted. If someone checks out a version of the code where that file existed, CVS will pull the file out of the Attic and allow it to be checked out. The funny thing was that my newly committed file still checked out normally even though it only existed in the Attic on the server.

Eventually, I concluded the trouble of moving a 1.12 repository to a server running CVS 1.11 wasn’t worth the trouble. I checked out the latest versions of all my projects, deleted the CVS meta-data from them (Eclipse allows you to do this automatically when you disconnect a project from a CVS repository), and then created a new clean repository and checked all the projects back in. My history information is gone, but if I ever discover I really need an old version of a file, I still have a copy of the original repository on my home server.

Making iTerm and naim play nicely

I’ve started using iTerm as my terminal client on Mac OS X. Previously, I was using the Terminal.app which comes with OS X, but that has its limitations. It also doesn’t look as pretty as iTerm does when I’m using naim, the console based messaging client I use to talk on IRC, GoogleTalk, and AIM.

Out of the box, iTerm works really well and there wasn’t very much I customized to make it look the way I wanted. I didn’t see an option in Terminal to disable bold fonts, however iTerm has that option and it makes naim look much nicer:

Of course, I can’t forget to mention one of the best features of iTerm: tabs! Yes, I can have five or six terminal windows open, and they will only take up the space of one window. Detaching a tab is as simple as dragging it away from the main window. OK, back to the point of this post.

When I started using a G4 Mac several months ago, I was using Terminal to access naim. After a lot of digging around on the web, I finally discovered how to map the keys in Terminal so they work as expected to control naim (changing screens, scrolling through the buddies list, etc). I documented my discoveries on the NaimWiki. However, to my disappointment and frustration, iTerm’s default key bindings did not work with naim out of the box. I figured it would be as simple as following the steps I followed for Terminal, but that wasn’t the case.

There were two problems I needed to solve: Fix the backspace key and the home, end, page up, and page down keys. The backspace key was Yfixed with the help of this blog post. I simply modified the ‘delete’ key mapping for the iTerm Keyboard Profile I was using and changed the hex code being sent from 7f to 0×08.

I then needed to add new entries for the other keys to work properly. For each of these keys, add a new mapping, select the key, choose ‘Send escape sequence’ for the action and enter the appropriate sequence:

[1~ (home)
[4~ (end)
[5~ (page up)
[6~ (page down)

That’s it! You should now be able to change connections (IRC, AIM, etc) by holding down fn and pressing delete. To scroll through your buddies, or through channels on IRC, simply hold fn and press home or end. To page up and down through the conversation window, use fn and page up or page down.

You can find my addition of this information to the NaimWiki here. If you have your own tips for using iTerm, please let me know!

Recovering from power outages with a Linux Mac Mini

My Debian GNU/Linux server Pluto, which is located in my apartment in Cambridge, is running on a Mac Mini (PowerPC). Over the past few days, I’ve had several power outages. When the power comes back on, my Windows computer turns back on and, if I need to, I can remotely connect to it from the office. Pluto however, does not automatically turn back on. I need to physically turn it on when I get home from work. This is not acceptable!

On a PC, there is a BIOS option called PWRON After PWR-Fail. This simply turns the computer back on if the power goes out while it is running. Great, but the Mac Mini doesn’t have a standard BIOS; it has OpenFirmware! I did lots of Googling and came up with solutions specific to Mac OS X, but that doesn’t help me since I’m running Linux. I even discovered the command line utility pmset which can be used to modify power management settings from within OS X (and a nifty option called autorestart which causes the Mac to automatically restart after power failure). I thought maybe I could find the pmset utility for Linux and install that, but that turned up nothing as well.

Eventually, I found the answer in this forum post on an Ubuntu forum. It’s amazing how the Ubuntu operating system has created such a huge wealth of information for the Linux community. This huge pool of questions and answers has made finding solutions to common (or not so common) Linux issues much easier over the past few years.

As root, execute the following command:

echo 'server_mode=1' > /proc/pmu/options

You can confirm the changes have been made by running:

cat /proc/pmu/options

If server_mode=1, then you’re all set. You can try unplugging the power from your Mac Mini, waiting a few minutes, and plugging it back in. The Mini should turn on as soon as you plug in the power.

mutt folderfilter errors

I’ve been slowly setting up mutt, along with offlineimap and fetchmail, to replace Thunderbird as my primary email client. One of the problems I ran into was with the offlineimap folderfilter option. I only wanted to sync a few specific folders on my IMAP server, instead of all of them. The way you do this with offlineimap is to use the folderfilter option. According to the offlineimap documentation, here is how you do it:


folderfilter = lambda foldername: foldername in [
      'Mail/INBOX',
      'Mail/list/zaurus-general',
      'Mail/list/zaurus-dev',
      ]

However, after copying and pasting that very same command (changing the folder names of course), I received the following errors:


Traceback (most recent call last):
File "/usr/bin/offlineimap", line 21, in ?
init.startup('4.0.8')
File "/usr/lib/python2.3/site-packages/offlineimap/init.py", line 79, in startup
config.read(configfilename)
File "/usr/lib/python2.3/ConfigParser.py", line 263, in read
self._read(fp, filename)
File "/usr/lib/python2.3/ConfigParser.py", line 484, in _read
raise e
ConfigParser.ParsingError: File contains parsing errors: /home/raam/.offlineimaprc
[line 34]: ‘]\n’

At first glance, those look like some pretty obscure errors. So what’s the problem? The new line character (\n)! During my copy and paste, hidden characters were copied over. After I put the entire folderfilter opton on a single line, I was able to move each mailbox entry down to it’s own line and run offlineimap without errors.

Not a very exciting problem (or solution), however this might find it’s way into someone’s Google search and resolve a not-so-obvious problem.

Recovering from CTRL+S in Putty

Every once in awhile, I’ll press CTRL+S by accident while I’m inside a terminal window. For the longest time, this simple accidental keystroke meant I had reconnect to my Linux server, kill whatever program I was running, and then start it again. Eventually I got sick of this happening and decided to do what I should have done in the first place: Google It.

Apparently CTRL-S actually does XOFF, which means the terminal will accept key strokes but won’t show the output of anything. It will appear as if your terminal is dead when it’s really just waiting to be turned back on. The fix? Simply press CTRL-Q to turn flow-control on (XON). If you pressed a whole bunch of keys before pressing CTRL+Q, you’ll see the output from those keystrokes.

In the Windows world, CTRL+S is used as the Save command. Over the years, I’ve developed the habit of pressing CTRL+S every few minutes while working on a document, simply because I’ve had too much work lost from stupid errors. Thankfully, this habit will no longer get in my way of working in the Linux world.

Kill Inactive and Idle Linux Users

Every once in awhile the SSH connection to my Linux server will die and I’ll be left with a dead user. Here’s how I discover the inactive session using the w command:

 15:26:26 up 13 days, 23:47,  2 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
raam     pts/0    wfc-main.wfcorp. Mon10    2days  0.04s  0.04s -bash
raam     pts/1    pool-151-199-29- 15:26    0.00s  0.02s  0.01s w

You can easily tell there’s an idle user by glancing at the IDLE column; the user in the first row has been idle for 2 days. There are many ways of killing idle users, but here I’ll show you a few of my favorites. The bottom line is, you need to kill the parent process created by the idle user when he logged in. There are a number of ways of doing that.

Here is how I discover the parent process using the pstree -p command:

        ├─screen(29380)───bash(29381)───naim(29384)
        ├─scsi_eh_0(903)
        ├─sshd(1997)─┬─sshd(32093)─┬─sshd(32095)
        │            │             └─sshd(32097)───bash(32098)─┬─mutt(32229)
        │            │                                         └─screen(32266)
        │            └─sshd(1390)─┬─sshd(1392)
        │                         └─sshd(1394)───bash(1395)───pstree(1484)
        ├─syslogd(1937)
        └─usb-storage(904)

We need to find the parent PID for the dead user and issue the sudo kill -1 command. We use the -1 option because it’s a cleaner way of killing processes; some programs, such as mutt, will end cleanly if you kill them with -1. I can see by looking at the tree where I’m running the pstree command, so I just follow that down the tree until I find a common process (branch) shared by both users; this happens to be sshd(1997).

You can see there are two branches at the point — one for my current session and one for the idle session (I know this because I’m the only user logged into this Linux server and because I know I should only have one active session). So I simply kill the sshd(32093) process and the idle user disappears.

Of course, if you’re on a system with multiple users, or you’re logged into the box with multiple connections, using the above method and searching through a huge tree of processes trying to figure out which is which will not be fun. Here’s another way of doing it: Looking at the output from the w command above, we can see that the idle users’ TTY is pts/0 so now all we need is the PID for the parent process. We can find that by running who -all | grep raam:

raam     + pts/0        May 10 10:45   .         18076 (wfc-main.wfcorp.net)
raam     + pts/1        May 11 15:26   .         1390 (pool-151-199-29-190.bos.east.verizon.net)

Here we can see that 18076 is the PID for the parent process of pts/0, so once we issue kill -1 18076 that idle session will be gone!

Erasing a Disk Using Linux

Here is a really quick way to erase a disk in Linux. Maybe “erase” is the wrong word — the command actually fills the entire disk with 0’s thereby overwriting any existing data. Assuming the disk you want to erase is /dev/hda, here’s what you would run:

dd if=/dev/zero of=/dev/hda bs=1M

Technically, this is a better option than simply “deleting” the data or removing the partitions, as those options make it easier to recover data. So, if the FBI is about to raid your little lab and you only have time to run one command, thats what it should be. :)

Linux Power on Windows Machines

The other day I needed to update a bunch of links inside several files for a website, which was hosted on a Windows 2000 server (ugh!). I had no idea which files needed to be updated, and there were well over 60 files. You may recall I had to do the very same thing a week earlier, however that website was hosted on a Linux machine.

Then I realized I had installed Cygwin on the Windows 2000 server awhile back, but never got around to using it! I copied and pasted the search and replace command I had used on the Linux machine and pasted it in the Cygwin console, changing the directory to the one I needed to search. Ten seconds later, all the files were updated!

After this event, I have a new found respect for Cygwin.

Recursively Renaming Multiple Files

I needed to rename a bunch of files for a customer at work the other day — more than 60 files. The files were on a Linux system, so I knew there was an easy way of doing it. A few days ago I used perl to search and replace a piece of text in a several files , so I decided to find a way to do it with perl.

I found the following script on this site:

[perl]
#!/usr/local/bin/perl
#
# Usage: rename perlexpr [files]

($regexp = shift @ARGV) || die “Usage: rename perlexpr [filenames]\n”;

if (!@ARGV) {
@ARGV = ;
chomp(@ARGV);
}

foreach $_ (@ARGV) {
$old_name = $_;
eval $regexp;
die $@ if $@;
rename($old_name, $_) unless $old_name eq $_;
}

exit(0);
[/perl]

After saving the script to a file called rename (and chmod 755‘ing it) I was able to run the following command to change the file extension on all .JPG files from uppercase to lowercase .jpg. To search for all files underneath a particular directory, I used the find command and piped it’s output to the rename script:

find /home/customername/content/images/ | rename 's/JPG$/jpg/'

A few seconds later and all the files were renamed! This script is incredibly versatile, as you can pass it any regular expression! A few quirks I found were that you cannot reference the script; you must use it from the directory you stored it in (find / | ~/rename 's/JPG$/jpg/' won’t work). This is because the script uses itself (on line 17). This also means if you save the script as something other than rename, you must also modify line 17 of the script.

Using rsync to Mirror two CVS Repositories

I have two personal Linux servers, named Mercury (located in Lowell, MA) and Pluto (located in Cambridge, MA). Monday through Friday I stay in my Cambridge apartment to be close to work and on the weekends I go back to Lowell.

I’ve been storing all of my projects, both work and personal, in a CVS repository on Mercury. A few weeks ago, however, there was a power outage in Lowell during the middle of the week and Mercury didn’t turn back on (probably because I don’t have the “PWRON After PWR-Fail” BIOS option set to Former-STS, if it even has that option). So, since the computer wasn’t on, I wasn’t able to commit or sync any of the projects I was working on. This would normally not be a problem, however I have several staging scripts setup on Mercury which I use frequently to test my work — so basically I was dead in the water.

After this incident, I realized I needed to mirror my CVS repository to prevent anything like that from happening again. This mirror would not only allow me to access the same CVS repository in the event that I was unable to reach one of my servers, but it would also act as a backup in case I somehow lost all the data on one of the servers.

After a little research using Google, I found this site which basically explains the -a option for rsync:

By far the most useful option is -a (–archive). This acts like the corresponding option to cp; rsync will:

* recurse subdirectories (-r);
* recreate soft links (-l);
* preserve permissions (-p);
* preserve timestamps (-t);
* attempt to copy devices (if invoked as root) (-D);
* preserve group information (-g) (and userid, if invoked as root) (-o).

Using that info, I ran the following command from Mercury (l.rd82.net is the DNS address I have mapped to it’s public IP, and c.rd82.net is mapped to Pluto’s public IP)

rsync -a /home/cvs raam@c.rd82.net:/home

That’s it! After waiting a few minutes (it took several minutes the first time) my entire CVS repository was copied to Pluto, my Cambridge Linux server. Of course, before I ran that command I had to create the CVS repository on Pluto first by running cvs init /home/cvs. After the rsync command completed, I added a CVS repository for c.rd82.net in Eclipse and confirmed that all my projects were there.

The only thing left to do is to setup a cron job to run the command every night. Of course, I’ll need to setup SSH keys so the rsync command can run without user input, but thats easy.

Installing Apache 1.3 on Debian Etch

I few days ago I setup a new Linux box to use for testing my web development work. The production environment for the site is hosted on a Linux machine, so I wanted to test it in a Linux environment, not a Windows environment (which is where I currently do my development work). So, I decided to setup a Linux box with Samba, map a network drive, and simply work on my site files directly from the Linux server. This way I can just save my changed file, press refresh in my browser, and see the changes. I’ll explain more about my actual staging setup in a future post.

I did not find very much, if any, information about how to easily setup Apache 1.3 on a Debian 4.0 (Etch) system. Why do I want Apache 1.3 instead of Apache 2? Because I’d like to replicate the production environment as closely as possible. My web host uses Apache 1.3.37, PHP 4.4.3, and MySQL 4.1.21.

I documented the steps I took to get everything setup here on my Wiki. This is the first time I’ve used the Wiki to store information that I would normally post here in my blog, and I’m still trying to figure out how I will decide what information goes on the Wiki and what goes on the blog.

The quick answer to getting Apache 1.3 installed on an Etch system is to edit /etc/apt/sources and change etch to sarge. Then run apt-get update and apt-get install apache. You can make sure you’re going to install Apache 1.3.X beforehand by running the following command: apt-cache showpkg apache and checking which version it displays. It should show something like this:

Package: apache
Versions:
1.3.34-4.1(/var/lib/apt/lists/debian.lcs.mit.edu_debian_dists_etch_main_binary-i386_Packages) (/var/lib/dpkg/status)

Easily Replace all Occurances of a String in Multiple Files Using Linux

This came in handy when I needed to change the IP address used in several links on a customer’s site. I simply specified the path to all the web files, and ran a command similar to the one below.

find /path/to/directory -name "*.txt" | xargs perl -pi -e 's/stringtoreplace/replacementstring/g'

Note that you can change *.txt to just * to search inside all files. Also keep in mind the replacement command will be completed recursively (files nested inside directories will also be searched).