Thursday, October 20, 2011

Update to Scripting with Ruby on OS X

After updating to lion, I needed to tweak the script I had written here, so here is the updated version with some more comments to help people modify and write their own Ruby/OSX scripts


#!/usr/bin/ruby
# -*- coding: utf-8 -*-

# Put this in Library/Scripts/Applications/Terminal
#
# To get the script to show up in menubar,
# run "AppleScript Editor" -> Preferences -> "Show script menu in menu bar"
#
# To figure out what properties are available, "AppleScript Editor" -> File -> "Open Dictionary"

require 'osx/cocoa'
include OSX
OSX.require_framework 'ScriptingBridge'

def alert(title, message='', is_error=false)
puts "Displaying #{is_error ? 'error ' : ''}dialog - #{title}: #{message}"
if is_error
NSRunCriticalAlertPanel(title, message, 'OK', nil, nil)
else
NSRunAlertPanel(title, message, 'OK', nil, nil)
end
end

rowSize = 24
colSize = 80
yMin = 22
xMin = 187
xCount = 2

# To get bundle ID, View Package Contents on App -> Contents/Info.plist -> Bundle ID
term = SBApplication.applicationWithBundleIdentifier_("com.apple.Terminal")

# set the # of rows and columns (global setting, so set to one tab, sets for all)
# term.windows.first.tabs.first.currentSettings.numberOfRows = rowSize
# term.windows.first.tabs.first.currentSettings.numberOfColumns = colSize
term.defaultSettings.numberOfRows = rowSize
term.defaultSettings.numberOfColumns = colSize

# sort on title to order according to ⌘N window selection shortcuts, index is some other order
sorted_windows = term.windows.sort_by {|w| w.name.to_s.match(/([0-9]+)$/)[1] }
sorted_windows.each_with_index do |window, i|
# puts "#{window.index}: #{window.name}"

# sizes should be the same for all windows after setting numberOfRows/Cols
xSize = window.bounds.width
ySize = window.bounds.height

newx = xMin + xSize * (i % xCount)
newy = yMin + ySize * (i / xCount).to_i

rect = NSRect.new(newx, newy, xSize, ySize)
window.bounds = rect

# If setting rect doesn't work, set position instead even though it is
# deprecated
# pos = NSPoint.new(newx, newy)
# window.position = pos
#
# size also deprecated
# size = NSPoint.new(xSize, ySize)
# window.size = size
end

Tuesday, August 24, 2010

JConsole via a SOCKS ssh tunnel

I've been learning how to use cassandra lately, and one of the most useful tools when working with it is to be able to query status via all the JMX hooks it exposes. At first I was using jmxterm to query all the available settings, and while this works great, it was tough as a cassandra newb to figure out which beans I needed to query.

What I wanted to do was use JConsole so that I could browse the available beans without having to know a priori what they were. Unfortunately, our production system is all remote (ec2), so this makes it tricky to use JConsole.

At first I tried to use a basic ssh port mapping, but this didn't work due to RMI's need to create its own ports in the JConsole runtime to which the monitored process tries to connect back to - only the single port is tunnelled by ssh. There are a number of documented solutions to doing this, but they all seemed very intrusive and hard to setup correctly (e.g. running a hacked jmx agent within your monitored process).

I then tried using JConsole running remotely but displaying on my local X server, but the performance wasn't that great, and its a pain to have to install all the X runtime on my production system just for running JConsole.

Eventually I smacked my forehead after remembering that ssh can also act as a SOCKs proxy, which works great for any application that supports it. Fortunately, JConsole does (in jdk 1.6 at least).

Here is the bash alias I use to setup the socks connection and connect to my cassandra instance via jconsole. I have multiple remote remote cassandra hosts, so this lets me run JConsole independently for each.


function jc {
host=$1
proxy_port=${2:-8123}
jconsole_host=wrongway
ssh -f -D$proxy_port $host 'while true; do sleep 1; done' ssh_pid=`ps ax | grep "[s]sh -f -D$proxy_port" | awk '{print $1}'`
jconsole -J-DsocksProxyHost=localhost -J-DsocksProxyPort=$proxy_port ser
vice:jmx:rmi:///jndi/rmi://${jconsole_host}:8181/jmxrmi
kill $ssh_pid
}


Once this was in my shell environment, I simply run "jc remote_host", and I get a jconsole connected to my cassandra instance on that host.

Note that you need to set "jconsole_host" in the alias for this to work for you. I'm not sure why, but Its sensitive to the value in here, and only worked for me for the first entry I had in /etc/hosts (not localhost) that maps my local hostname to 127.0.0.1

Wednesday, February 10, 2010

Automating gists with QuickSilver

I like to use gist for sharing code snippets. I like to use QuickSilver for automating common tasks. To combine the two is fairly simple:
  1. Install the gist gem
  2. gem install gist

  3. Enable the Terminal Module in QuickSilver Plug-ins
  4. Create a new HotKey Trigger
  5. Hit "." then type the following command into the Select Item field. You only need the bash part if you installed the gist gem using rvm/ruby instead of the system ruby
    bash -l -c "pbpaste | gist | pbcopy"
  6. Tab over to Action field, and select "Run Command in Shell"
  7. Save the trigger, then set whatever hotkey you want - I use Cmd-Shift-G - you might need to restart quicksilver for this to take effect
Now you can select some text, copy to clipboard, then hit Cmd-Shift-G to create a gist from the clipboard contents, ending up with a url to that gist now in the clipboard.

Wednesday, April 22, 2009

Mysql connect/read/write timeouts in rails

Ran into a problem in production recently where mysql/OS ran out of memory, didn't die, but didn't actually do anything, so connections to it were still open. As a result, the rails mysql adapter would just hang indefinitely in the middle of a request to mysql, with no exception being thrown, thereby not triggering our db failover and causing our entire site to come to a screeching halt!

In order to prove my hypothesis correct, not to mention to have a way to test that I'd fixed the problem, I first had to be able to duplicate this problem, i.e. I had to simulate mysql hanging without actually killing it. Fortunately *nix has a good way of doing this via kill:

To stop a process: kill -s STOP mysqld_pid
To resume it: kill -s CONT mysqld_pid

This duplicated the problem perfectly - any ActiveRecord::Base.execute would hang indefinitely.

Digging around some, I discovered that the ruby mysql gem does have a way to set timeouts, but this config is not exposed in database.yml, so I dutifully forked rails (yay github!), patched it, and solved my problem.

Here's the Rails issue from which you can get the patch

Saturday, April 4, 2009

Added EBS support to rubber

I recently committed a change to rubber that adds some basic support for Amazon EC2's persistent volumes (Elastic Block Store, EBS). The way it works is that you tell rubber the host volume configurations, and those volumes will get created/formatted/mounted automatically on those hosts. If the host gets destroyed and recreated, the volume will get remounted without being formatted.

For example, in rubber.yml you'd have something like:

hosts:
db01:
availability_zone: us-east-1a
ec2_volumes:
- size: 100 # size of vol in GBs
zone: us-east-1a # zone to create volume in, needs to match host's zone
device: /dev/sdh # OS device to attach volume to
mount: /mnt/mysql_data # The directory to mount this volume to
filesystem: ext3 # the filesystem to create on volume


When you first create/bootstrap the db01 hostname, the volume will automatically get created, formatted and mounted to the given path on the instance. If you then destroy db01, you're given the option to cleanup the volume, but if you don't, recreating db01 will automatically re-attach and remount the volume (without formating it).

This addresses the use case (mine) where you assign persistent storage to say a DB server, and want to be able to quickly rebuild that instance if it dies, without having to figure out which ec2 volume IDs map to which instance IDs. Just make sure you answer NO to destroying the instance if you do a rubber:destroy and want to keep the volume around for when you recreate the instance.

I also changed Elastic IPs to work in a similar fashion - you specify you want an instance to have an elastic ip, and rubber allocates and assigns the static ip to that instance, remembering the IP/instance mapping so that if you destroy/recreate the instance, it will get back the same IP.

Similar to the volumes support, when you destroy an instance you get prompted to cleanup the IP, but if you answer NO it keeps the mapping around to reassign it to the instance at recreation time.
e.g.
hosts:
web01:
use_static_ip: true

Tuesday, February 10, 2009

Time Capsule restores from a secure shared disk

My macbook pro died this week. Turns out my logic board went caput, so they had to put in a new one. In the interim, I had a spare I could use, but first I had to do a full system restore from my backup on our Time Capsule.

We have our time capsule setup for secure shared disks with accounts so that each user has their own password protected private share so that no other user can snoop on their backup.

Unfortunately, the Full System Restore available when booting the Leopard DVD failed to see my backup even though I used my private time capsule account credentials when connecting to it. All I could see were other backups that were available on the public share, leading me to believe that my credentials were working to mount the image, but the restore app wasn't smart enough to mount my private mount point instead of (or in addition to) the public one. I tried calling apple support, and after escalating the issue, all they could suggest was to copy my backup from the private share to the public one. Needless to say, it takes a really long time to a couple of hundred GB even when connected directly to the time capsule using an ethernet cable.

Fortunately, when booting from the Leopard DVD, one also has access to a Terminal. By manually mounting the private share from Terminal, the full system restore UI was then able to see my private share and the restore was able to take place.

To Summarize, Boot from the Leopard DVD, select Utilities->Terminal, then use the mount command to mount your private time capsule share. In my case, my username and share are named the same - mconway - I think this is just the way time capsule works, so the pattern should be the same for you. Also, I had to add the .local to the end of the time capsule's name for the mount to work.


# Create directory to mount to
mkdir -p /Volumes/mconway
# Do the mount
mount -t afp 'afp://mconway:mypassword@my-time-capsule-name.local/mconway' /Volumes/mconway


Once mounted, you can verify with a "ls /Volumes/mconway" and you should see the sparseimage for your backup. Quit Terminal, go to Utilities->Full System Restore, and your backup should be visible, though you might have to lead the UI through the hoops by connecting to your time capsule and typing in your credentials again.

Hopefully this will save someone else the hours of grief I was faced with.

Tuesday, December 30, 2008

Converting to blogger from wordpress

The HTPC I was running wordpress on got replaced with an appletv+boxee (boxee rocks btw), and rather than waste electricity just to run my blog, I figured I'd convert it to a hosted solution. I don't want to have to do this again any time soon, so I wanted to use a hosted service thats pretty reliable and will be around for a while. I didn't feel like paying wordpress for the privilege of mapping a custom domain, so blogger it was. Problem was, I couldn't get my wordpress export imported into blogger. I tried wxr2blogger, but while the script ran fine, blogger wouldn't actually import the results. I also found a ruby script which seemed to work, but wouldn't pull my comments across, so I ended up tweaking it so that it would work with comments as well. Here is my improved wordpress2bloggger.rb script.