Skip to main content
Posts by:

Tim Booher

April 2011 Server Setup with Rails, Ubuntu, Nginx, RVM, Capistrano, Git

There are other blogs out there telling you how to set up a ruby
server install with ubuntu, nginx, passenger, linode ubuntu 10.10, and
rvm, but I couldn’t find one that put it all together, so I decided to
share my setup from beginning to end. I hope it helps you, but I’m also
hoping you point out any risks that I should clean up.

In general I used “this blog”:http://thekindofme.wordpress.com/2010/10/24/rails-3-on-ubuntu-10-10-with-rvm-passenger-and-nginx/, but it didn’t have everything I needed, as you’ll see below.

First I set up the server in linode, then connected to the server via a simple ssh command:

ssh root@tacitus

Before anything else, I had to install basic applications such as git and emacs.

sudo apt-get install git emacs

I decided that I wanted to set up everything from the perspective of
the deployment user, passenger, so I created the user *passenger*:

root@li289-94:~# useradd -d /home/passenger -m passenger
root@li289-94:~# usermod -s /bin/bash passenger
root@li289-94:~# passwd passenger

Now set-up ssh with public keys to enable password free auto-connection. Then I logged in as root, added passenger
to sudoers (using visudo and adding passenger to the sudo group) and started getting ready to install the ruby
version manager, _rvm_.

bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)

Now, we need to load RVM into a shell session to .bashrc to make the rvm executables available.

added [[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"

To prevent restarting the shell, you can run source ~/.rvm/scripts/rvm to get everything up and running immediately.
In order to test the installation, I tried ‘type rvm | head -n1’ to be sure rvm was a function available to the user.

Now, it’s time to get everything ready for the heavy lifting a ruby install requires.

sudo aptitude install build-essential bison openssl libreadline5
libreadline5-dev curl git-core zlib1g zlib1g-dev libssl-dev
libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libmysqlclient-dev

And I forgot to run (needed later):

sudo apt-get install libxslt-dev libxml2-dev libcurl4-openssl-dev

And the big moment, the chance to let rvm handle the latest ruby. (This will take a good bit of time.)

rvm install 1.9.2
rvm use 1.9.2 --default

With 1.9.2 installed, we can now configure passenger and create a gemset for our project

rvm 1.9.2 --passenger
rvm gemset create polco
rvm 1.9.2@polco
gem install passenger
rvmsudo passenger-install-nginx-module

Stick with the passenger defaults. The only problem was on rvm 1.9.2 –passenger, I get:

NOTE: If you are using Passenger 3 you no longer need the passenger_ruby,
use the wrapper script for your ruby instead (see 'rvm wrapper')

This was a new one to me, and made me deviate from the script I was following. I am using passenger-3.0.6, so I am definitely more up to date than the server. Due to this error, I broke with the blog I was following and did not change passenger_ruby to:

passenger_ruby /home/wayne/.rvm/bin/passenger_ruby;

This might still be something I need to do since I am currently getting a 404. I am changing back to this — which for me is the following line:

passenger_ruby /home/passenger/.rvm/bin/passenger_ruby;

h3. Passenger Configuration

gem install passenger

I chose, “compile and install nginx for me” and chose default install
locations. This takes a good while too. Now it was time to set up
nginx.

The installer told me that my configuration was already modified and
that:

This installer has already modified the configuration file for you! The
following configuration snippet was inserted:

  http {
      ...
      passenger_root /home/passenger/.rvm/gems/ruby-1.9.2-p180/gems/passenger-3.0.6;
      passenger_ruby /home/passenger/.rvm/wrappers/ruby-1.9.2-p180/ruby;
      ...
  }

To prepare for the deployment, I made the deployment directory:

mkdir -p /home/passenger/polco/public/;

So now I set up a basic server with the following nginx.conf file:

worker_processes  1;
 error_log /var/log/nginx/error.log debug;

 events {
    worker_connections  1024;
 }

 http {
    passenger_root /home/passenger/.rvm/gems/ruby-1.9.2-p180/gems/passenger-3.0.6;
#    passenger_ruby /home/passenger/.rvm/wrappers/ruby-1.9.2-p180/ruby;
    passenger_ruby /home/passenger/.rvm/bin/passenger_ruby;

    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {
      listen 80;
      server_name localhost;
      root /home/deployer/polco/public;
      passenger_enabled on;
   }

 }

And I installed an nginx start script:

cd
git clone git://github.com/jnstq/rails-nginx-passenger-ubuntu.git
sudo mv rails-nginx-passenger-ubuntu/nginx/nginx /etc/init.d/nginx
sudo chown root:root /etc/init.d/nginx

Now I am making a single-user git repo:

mkdir ~/polco.bare.git/
cd polco.bare.git/
git init --bare

So I could now run from my desktop:

git push ssh://passenger@tacitus/~/polco.bare.git/ master

Everything worked . . . so far, now for the hard part . . .

h3. Deployment Setup

On the server, installed the bundler gem (under the current rvm: ruby 1.9.2@polco) and put together the following deploy.rb with Capistrano.

 $:.unshift(File.expand_path("/home/tim/.rvm/lib"))
 require 'rvm/capistrano'
 set :rvm_ruby_string, '1.9.2'
 set :rvm_type, :user
 require 'bundler/capistrano'
 set :application, "polco"
 role :web, "66.228.39.94"
 role :app, "66.228.39.94"
 role :db,  "66.228.39.94", :primary => true
 default_run_options[:pty] = true
 ssh_options[:forward_agent] = true
 set :deploy_to, "/home/passenger/polco"
 set :deploy_via, :remote_cache
 set :user, "passenger"
 set :use_sudo, false
 set :scm, :git
 set :scm_username, "passenger"
 set :repository, "ssh://tim@tacitus/~/polco.git/"
 set :branch, "master"
 set :git_enable_submodules, 1
 namespace :deploy do
  task :start, :roles => :app do
    run "touch #{current_path}/tmp/restart.txt"
  end

  task :stop, :roles => :app do

  end

  desc "Restart Application"
  task :restart, :roles => :app do
    run "touch #{current_path}/tmp/restart.txt"
  end

  desc "Symlink shared resources on each release - not used"
  task :symlink_shared, :roles => :app do

  end
 end

p. after ‘deploy:update_code’, ‘deploy:symlink_shared’

Then, from my desktop, I built the necessary directories and tested my deployment.

cap deploy:setup
cap deploy

The deployment works fine, and I know I still need to get my databases setup on the server for everything to run correctly. But I am surprised that nginx gives me a 404.

2011/04/11 22:52:00 [notice] 20057#0: signal 17 (SIGCHLD) received
 2011/04/11 22:52:00 [notice] 20057#0: worker process 20059 exited with code 0
 2011/04/11 22:52:00 [notice] 20057#0: exit
 2011/04/11 22:52:00 [notice] 20712#0: using the "epoll" event method
 2011/04/11 22:52:00 [notice] 20712#0: nginx/0.8.54
 2011/04/11 22:52:00 [notice] 20712#0: built by gcc 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)
 2011/04/11 22:52:00 [notice] 20712#0: OS: Linux 2.6.38-linode31
 2011/04/11 22:52:00 [notice] 20712#0: getrlimit(RLIMIT_NOFILE): 1024:1024
 2011/04/11 22:52:00 [notice] 20752#0: start worker processes
 2011/04/11 22:52:00 [notice] 20752#0: start worker process 20753
 2011/04/11 22:52:16 [error] 20753#0: *1 "/home/deployer/polco/public/index.html" is not found (2: No  such file or directory), client: 71.191.237.168, server: localhost, request: "GET / HTTP/1.1", host:  "tacitus"

Please let me know of any comments or ways I can improve my setup. Also, I would love to hear if any of this was helpful for you.

By 0 Comments

Building Plots in Visio

I like to automate Microsoft Visio to draw plots. They look so much more professional and I don’t have to fuss with Excel to figure out how to make plots look like I want. Again, this is me practicing my theory of being in control, not letting software control us. Please let me know if this code is useful.

This is my source code for the stacked bar plot I’m working on. Built as an excel Macro, it automates Visio to build the plot.


Sub BuildPlot()

Dim oApp As Excel.Application
Dim oWB As Excel.Workbook

Dim oDoc As Visio.Document
Dim oPage As Visio.Page
Dim oShape As Visio.Shape

Set oApp = New Excel.Application
Set oWB = oApp.Workbooks.Open("c:usersTimothy.BooherDesktopbar_plot_macro.xlsx")

Set oDoc = Application.ActiveDocument
Set oPage = Application.ActivePage

Dim iRow As Integer
Dim iAircraft As Integer
Dim x As Double
Dim x_prime As Double
Dim y As Double
Dim y_prime As Double
Dim xWidth As Double
Dim xGap As Double
Dim iStart As Integer

xWidth = 0.5
xGap = 2
x = 0
iStart = 2

For iAircraft = iStart To (iStart + 4)
    Debug.Print oWB.sheets(1).Cells(iAircraft, 2).Value
    x = x + xGap
    x_prime = x + xWidth
    y_prime = 0
    For iRow = 7 To 4 Step -1
        Debug.Print oWB.sheets(1).Cells(iAircraft, iRow).Value
        If oWB.sheets(1).Cells(iAircraft, iRow).Value > 0 Then
            y = y_prime
            y_prime = y + oWB.sheets(1).Cells(iAircraft, iRow).Value * 0.8
            Set oShape = oPage.DrawRectangle(x, y, x_prime, y_prime)
            oShape.Cells("FillForegnd") = 8 - iRow
        End If
    Next iRow
Next iAircraft

oWB.Close

Set oApp = Nothing

End Sub

Sub draw_axes()

Dim oDoc As Visio.Document
Dim oPage As Visio.Page
Dim oShape As Visio.Shape

Set oDoc = Application.ActiveDocument
Set oPage = Application.ActivePage

Dim i As Integer ' y
Dim j As Integer ' x

Dim numX  As Integer
Dim numY As Integer
Dim max_val As Double
Dim y_new As Double

numX = 10
numY = 10

max_val = 15 ' Ceiling(14.65)

' draw y axis
oPage.DrawLine 0, 0, 0, max_val

y_new = 0

For i = 1 To numY + 1
 'DrawLine
 oPage.DrawLine 0, y_new, 0.1, y_new
 y_new = y_new + max_val / numY
Next i

' draw x
'For j = 1 To numX
'
'Next j


End Sub

Sub build_conf_intervals()

Dim oDoc As Visio.Document
Dim oPage As Visio.Page
Dim oShape As Visio.Shape

Set oDoc = Application.ActiveDocument
Set oPage = Application.ActivePage

Dim iAircraft As Integer
Dim iBar As Integer

Dim y(1) As Double
delta_x = 3
to_wit = 0.1

x = 0

For iAircraft = 16 To 20
    x = x + delta_x
    For iBar = 2 To 3
        y(iBar - 2) = oWB.sheets(1).Cells(iAircraft, iBar).Value
    Next iBar
    oPage.DrawLine x, y(0), x, y(1)
    oPage.DrawLine x - to_wit, y(1), x, y(1)
    oPage.DrawLine x - to_wit, y(0), x, y(0)
Next iAircraft

oWB.sheets(1).Cells(iAircraft, 2).Value

End Sub

By 0 Comments

MacBook Local Link Address — can it be fixed?

I have a bad, bad networking problem with my wife’s MacBook. She can’t connect to the internet anywhere, but I am working with my Netgear WNDR3300 for this problem.

Our wireless router is dd-wrt based (open source router firmware), and works fine for about 20 other nodes (other laptops, guests, mobile devices, etc). A couple of details were helpful to start with. Her MacBook has three interfaces: en0, en1, and fw0.

* en0 Ethernet Network Interface Controller (NIC)
* en1 WiFi Network Interface Controller (NIC)
* fw0 Firewire Networking

The second interface (en1) got an ip of 196.254.153.219 which is a link local address (anything in 169.254.0.0/16) is supposed to mean I didn’t get an ip from the DHCP server. However, I could see 192.168.1.1, which is the local ip for the router. This confused me, because it told me that the radio was working and I was getting some type of ip, ah I just don’t understand how I could get a partial connection to the router.

So I went into the Airport Utility, and got the message that AirPort Utility was unable to find any wireless devices. What! With a connection to the router? I was still confused. How was this possible. I was thinking about trying to reseat the wireless card, looking at buying a new wireless card, but that seemed like a lot of work that might not even solve the problem.

Edit, the Airport Utility is not for wireless interfaces, it is for Airport Express/AppleTV like items

I tried to ping google’s DNS server 8.8.8.8, with no host.

The first thing I did was try to manually set the IP to avoid a DHCP error, but the Airport utility said I had no devices.

I was able to ping the wan port (172.27.35.xxx), but I couldn’t ping the public ip (74.96.53.xxx) from verizon dsl. So I thought the issue was my modem, but the laptop was having connection problems, so I figured I had to reseat the wireless card. OSX said I had an AirPort extreme, Broadcom BCM43xx 1.0 (5.10.91.21). Hmm . . . looks connected, but maybe it’s loose? I’m out of options.

From the software, I found my wife has a MacBook5,2, which from Wikipedia is the MB881*/A with the Integrated 802.11a/b/g and draft-n (n enabled). I followed the guide here to confirm the card is in correctly: http://www.ifixit.com/Guide/Repair/Installing-MacBook-Core-2-Duo-AirPort-Card/519/ and everything checked out just fine. That took a lot of time, but confirmed that the card is not loose.

Then I went into network settings and set a manual ip. My subnet mask is 255.255.255.0 with a dhcp server serving from 192.168.1.100 with 50 maximum dhcp users. I am also using opendns for content filtering and speed advantages. So I went into network settings and assigned an IPv4 address of 192.168.1.80 with the correct subnet and router address. I next went to terminal and used ifconfig to see my ip on en1 was 192.168.1.80 and broadcast of 192.168.1.255, media was listed as autoselect status: active.

And . . . I got ping returns from 8.8.8.8 and 4.2.2.2 — name servers I used for testing. However, my default test mit.edu returned nothing (the same with www.google.com). I was able to load http://74.125.225.18 (the site for google).

O.K. so then i knew it was a DNS problem. I hit the terminal with dscacheutil -flushcache to flush the cache, and then restarted. I was worried in that I couldn’t find my NAS on my network, but I hoped a restart would cure all ills. It didn’t: still no www.mit.edu or www.yahoo.com. Hmm . . . Branes from #mac on irc recommended that I manually add the DN servers from OpenDNS. I added the standard 208.67.222.222 and 208.67.220.220 in the advanced mac en1 settings . . . and . . . working great.

Still don’t know why I was getting a local link address. Oh well, it is working just fine now. Any thoughts appreciated.

By 0 Comments

Recovering Rails Data Using Production.log

So you didn’t set up automatic backups and need to recover your data? Don’t try digging it out of mysql, look no further than your rails production log. OK, try a bit with mysql, because the rails production log is a terrible way to recover your data. Only use as a last resort, but when data don’t exist anywhere else, this is what you have to do. While simple in concept (scrape the log, updated the database using ActiveRecord) this was very trick in implementation. I know my code could use some refactoring, but it is working pretty well right now.

So here is my code if anyone wants to do something similar.

I had a production log in the general format of:


Processing AttendanceController#update_exercises (for 76.105.103.226 at 2010-07-07 20:33:26) [POST]
  Session ID: 06af5da80fc41be243e42254cb776212
  Parameters: {"commit"=>"Record User Scores", "meeting_id"=>"3388", "exertions"=>{"exercise_note_437"=>"7:00 AM", "exercise_score_884"=>"6:52", "exercise_score_1120"=>"9:29", "exercise_score_1175"=>"13:21", "exercise_score_268"=>"8:07", "exercise_score_433"=>"6:29", "exercise_note_1200"=>"", "exercise_note_1190"=>"", "exercise_score_741"=>"11:02", "exercise_note_1191"=>"", "exercise_score_544"=>"8:37", "exercise_score_918"=>"***", "exercise_note_1124"=>"", "exercise_note_1069"=>"", "exercise_score_479"=>"9:18", "exercise_score_908"=>"8:04", "exercise_score_1012"=>"7:34", "exercise_score_105"=>"8:43", "exercise_note_417"=>"", "exercise_id"=>"25", "exercise_note_968"=>"", "exercise_note_913"=>"", "exercise_score_1200"=>"8:50", "exercise_note_1115"=>"", "exercise_score_437"=>"7:16", "exercise_score_1124"=>"9:32", "exercise_note_705"=>"", "exercise_score_1190"=>"12:26", "exercise_note_342"=>"", "exercise_score_1069"=>"9:34", "exercise_score_1191"=>"10:45", "exercise_note_1095"=>"", "exercise_note_872"=>"", "exercise_score_1115"=>"8:12", "exercise_score_417"=>"9:00", "exercise_note_884"=>"", "exercise_score_968"=>"9:02", "exercise_score_913"=>"7:59", "exercise_note_268"=>"", "exercise_note_433"=>"", "exercise_note_1130"=>"", "exercise_note_1031"=>"", "exercise_score_342"=>"9:26", "exercise_note_741"=>"", "exercise_note_1120"=>"", "exercise_note_544"=>"", "exercise_note_1175"=>"", "exercise_note_918"=>"", "exercise_score_1095"=>"6:59", "exercise_score_705"=>"7:29", "exercise_note_479"=>"", "exercise_note_908"=>"", "exercise_score_872"=>"8:25", "exercise_note_105"=>"", "exercise_score_1130"=>"8:14", "exercise_score_1031"=>"7:24", "exercise_note_1012"=>""}}
Redirected to actionindexid3388
Completed in 536ms (DB: 999) | 302 Found [http://www.fitwit.com/attendance/update_exercises]


Processing AttendanceController#index (for 76.105.103.226 at 2010-07-07 20:33:27) [GET]
  Session ID: 06af5da80fc41be243e42254cb776212
  Parameters: {"id"=>"3388"}
Rendering template within layouts/application
Rendering attendance/index
Completed in 195ms (View: 172, DB: 6) | 200 OK [http://www.fitwit.com/attendance/index/3388]

So I wrote this code to fix it. Basically, well, it’s complicated, if you have any questions, email me.


#!/usr/bin/env /Users/Tim/Sites/fitwit/script/runner

class CommandString
  attr_accessor :date, :method, :command

  def initialize(date, method, command)
    @date = date
    @method = method
    @command = command
  end
end

class ExerciseArray
  attr_accessor :exercise_id, :time_slot_id, :meeting_id, :values_hash

  def initialize(exercise_id, time_slot_id, meeting_id, values_hash)
    @exercise_id = exercise_id
    @time_slot_id = time_slot_id
    @meeting_id = meeting_id
    @values_hash = values_hash
  end
end

class RecoverData "Update", ("user_ids"=>[.*], "meeting_id"=>".*")/
        params = gen_hash("{#{$1}}")
        j+=1
        c = CommandString.new(@the_date,'take_attendance', params)
        commands <"(d+)", "commit"=>"Apply to ([ws]*)", "meeting_times"=>[(.*)], "id"=>"(d+)"}/
        boot_camp_id = $1.to_i
        if boot_camp_id = 40
          commit = "Apply to #{$2}"
          meeting_times = eval("[#{$3}]")
          time_slot_id = $4.to_i
          k+=1
          c = CommandString.new(@the_date,'manage_meetings', [time_slot_id, commit, meeting_times, boot_camp_id]) if boot_camp_id == 40
          commands <"Record User Scores", "meeting_id"=>"([0-9]+)", "exertions"=>({.*})}/
        meeting_id = $1.to_i
        #puts meeting_id
        attendees = gen_hash($2)
        i+=1
        c = CommandString.new(@the_date,'update_exercises', [meeting_id, attendees])
        commands < mt)
            ts.meetings < mt)
          @time_slot.meetings < user_id, :meeting_id => meeting_id) if submitted_user_new?(user_id, existing_attendee_ids)
          end
          # now deletes (for each existing user, see if they are excluded)
          existing_attendee_ids.each do |existing_id|
            if should_delete?(existing_id, post_user_ids)
              @meeting_user = MeetingUser.find_by_user_id_and_meeting_id(existing_id, meeting_id)
              @meeting_user.destroy
            end
          end
          puts "successful! attendance taken"
        else
          puts "bootcamp 41 or something, so no attendance taken"
        end
      else
        puts "attendance: #{meeting_id}"
      end
    else
      # need to raise exception
      puts "meeting id is nil for #{params.inspect}"
    end
  end

  def update_exercises(meeting_id, attendees)
    puts "updating exercises"
    # this is where we update our exercises which is the exertions table
    # the meeting _might_ not exist
    #begin
      if Meeting.exists?(meeting_id)
        meeting = Meeting.find(meeting_id)
        if meeting.time_slot.boot_camp.id == 40
          puts "!! found #{meeting_id}"
          users =  attendees.keys.map{|m| $1 if m.match(/exercise_score_(d+)$/) }.delete_if{|n| n.nil?} #  meeting.meeting_users
          exercise = Exercise.find(attendees["exercise_id"])
          users.each do |u_id|
            u = User.find(u_id)
            unless attendees["exercise_score_#{u.id}"].blank?
              ex = Exertion.new
              mus = MeetingUser.all(:conditions => ["meeting_id = ? and user_id = ?", meeting.id, u.id])
              if mus.empty? # then, we need to create an attendence
                mu = MeetingUser.create(:user_id => u.id, :meeting_id => meeting.id)
                puts "created attendance event for #{u.full_name}"
              else
                mu = mus.first
              end
              ex.meeting_user_id = mu.id
              ex.exercise_id = exercise.id
              ex.score = attendees["exercise_score_#{mu.user.id}"]
              raise "user: #{mu.user.id} mu: #{mu.id} ex: #{exercise.id}" if attendees["exercise_score_#{mu.user.id}"] == ''
              ex.notes = attendees["exercise_note_#{mu.user.id}"]
              ex.save
            end
          end
          puts "Recorded scores for #{exercise.name}"
          puts meeting.meeting_date
        else
          puts "wrong boot-camp #{meeting.time_slot.boot_camp.id}"
        end
      else
        puts "exercises:  #{meeting_id}"
      end
    #rescue

     # raise "bad: can't pull it off"
    #end
  end

  def submitted_user_new?(submitted_id,existing_ids)
    # if submitted_id is not in existing id's it is new
    !existing_ids.include?(submitted_id)
  end

  def should_delete?(existing_id,submitted_ids)
    #if existing id is not in submitted id it should be deleted
    !submitted_ids.include?(existing_id)
  end

end

BootCamp.find(40).time_slots.map{|ts| ts.meetings.destroy_all}

r = RecoverData.new()
commands = r.get_data
puts "about to run commands"
attendance_cursor = {98 => 0, 99 => 0, 100 => 0, 101 => 0}
exercise_cursor = {98 => 0, 99 => 0, 100 => 0, 101 => 0}
ea = []
commands.each do |c|
  case c.method
  when "take_attendance"
    user_id = c.command["user_ids"].first
    ts_all = User.find(user_id).registrations.map{|reg| reg.time_slot}
    ts = ts_all.select{|ts| ts if ts.boot_camp_id == 40}.first
    unless ts.nil?
      ts_id = ts.id
      bc = ts.boot_camp.id
      meeting_id = ts.meetings.map{|m| m.id}[[attendance_cursor[ts_id],23].min]
      #puts "meeting id is #{meeting_id}"
      attendance_cursor[ts_id]+=1
      puts "#{ts_id}, #{bc}, #{c.command["meeting_id"]}, #{c.date}, #{c.method}, #{c.command["user_ids"].to_sentence}"
      r.do_attendance(c.command, meeting_id)
    else
      puts "no timeslots for #{user_id}, but they are in bootcamps #{ts_all.map{|ts| ts.boot_camp.id}.to_sentence}"
    end
  when "manage_meetings"
    #  [time_slot_id, commit, meeting_times, boot_camp_id]
    if c.command.last == 40
      #puts "#{c.date}, #{c.method}, #{c.command.to_sentence}"
      r.manage_meetings(c.command[0], c.command[1], c.command[2], c.command[3])
    end
  when "update_exercises"
    # [meeting_id, attendees]
    params = c.command[1]
    params.first.first.match(/(d+)$/)
    user_id = $1 || 399 # this is to account for a wierd record that has an exercise if
    u = User.find(user_id)
    ts = u.registrations.map{|reg| reg.time_slot}.select{|ts| ts.boot_camp.id == 40}.first
    ex_id = params["exercise_id"]
    puts "!! #{ex_id}"
    unless ts.nil?
      ts_id = ts.id
      puts "time slot: #{ts_id}"
      m_index = [exercise_cursor[ts_id],23].min
#      puts "m index #{m_index}"
#      puts "meetings #{ts.meetings.inspect}"
      meeting_id = ts.meetings.map{|m| m.id}[m_index]
#      puts "meeting id: #{meeting_id}"
      exercise_cursor[ts_id]+=1
      # :exercise_id, :time_slot_id, :meeting_id, :values_hash
      ea << ExerciseArray.new(ex_id, ts_id, meeting_id, params)
      #puts "#{c.date}, #{c.method}, #{meeting_id}, #{c.command[1].inspect}"
      #r.update_exercises(meeting_id, c.command[1])
    else
      puts "all time slots are empty for bc 40 for user #{u.id} (they are in another boot_camp)"
    end
  end
end
puts "doing it"
ea.group_by{|e| [e.exercise_id, e.time_slot_id] }.each do |e|
  values_hash = {}
  meeting_id = e[1].first.meeting_id
  puts e.inspect
  e[1].each do |poopa|
    values_hash = values_hash.merge(poopa.values_hash)
  end
  puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
  puts "#{meeting_id}, #{values_hash.inspect}"
  puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
  r.update_exercises(meeting_id,values_hash)
end

By 0 Comments

Summer Training Plan

My summer/fall 20 week training plan which is all focused around the Marine Corps Marathon. All long runs will be done with the DC Road Runners. Details on the long runs are at DC Road Runners’ page. CF stands for crossfit workouts found at CrossFit Adaptation. Track workouts done with DC Road Runners Track Workouts

Goal is to qualify for Boston Marathon and to be doing all CF workouts as Rx’d by October 31.

Week Mon Tue Wed Thur Fri Sat Sun
1 CF ? CF ? ? LSD: 12|UP Upton Hill 2 m run
2 CF 20 mi bike 3 x 1600M @ C/I    400M recovery swim CF LSD: 14|Southeast w Key for 14-ish mi. 2 m run + swim
3 5 m run CF+3 m run 6-8 x 500M @ I/P   300M recovery CF CF+5 m pace LSD: 10|Fort Scott Hill 3 m run+ swim
4 5 m run CF+3 m run Ladder:  800M – 400M – 200M – 1000M; Half the distance in recovery.  2 sets with 4:00 break. swim CF+5 m run LSD: 13.5|Piney Branch w Memorial Bridge Return 3 m run+ swim
5 6 m run CF+3 m run RACE NIGHT.  Track:  6-8 x 600M @ I/P.  400M recovery. CF CF+6 m pace LSD: 15|Iron Triangle 3 m run+ swim
6 6 m run CF+3 m run RACE NIGHT.   Track:  12-14 x 400M @ C/I  100M recovery. CF CF+6 m pace LSD: 16|Battery Kemble Loops 3 m run+ swim
7 7 m run CF+3 m run TRACK RACES [TENTATIVE].  If so, no workout.  CF CF+7 m run LSD: 13.5|Glover/Mass/Capital Crescent 3 m run+ swim
8 7 m run CF+3 m run 3-4 x 1600M @ T/I  400M recovery.  (DCRRC race on 8/3) CF CF+7 m pace LSD: 14|Marymount Chain Bridge 3 m run+ swim
9 8 m run CF+4 m run 6-8 x 500M @ I/P   300M recovery CF CF+8 m pace LSD: 17|Arlington Triangle 3 m run+ swim
10 8 m run CF+4 m run 12-16 x 400M @ C/I  100M recovery CF CF+8 m run LSD: 13|Cathedral/Clintons 3 m run+ swim
11 9 m run CF+4 m run 4-5 x 1000M @ I/P  400-600M recovery.  CF 9 m pace LSD: 16|16 Mile Sidewalks MCM 3 m run+ swim
12 9 m run CF+4 m run Track Workout CF 9 m pace LSD: 18|Battery Kemble Loops 3 m run+ swim
13 10 m run CF+5 m run Track Workout CF 10 m run LSD: 20|C&O Out/Back 4 m run+ swim
14 6 m run CF+5 m run Track Workout CF 6 m pace LSD: 14|Ross Drive 4 m run+ swim
15 10 m run CF+5 m run Track Workout CF CF+10 m pace LSD: 22|Capital Crescent/Rock Creek 4 m run+ swim
16 6 m run CF+5 m run Track Workout CF 6 m run LSD: 14|Marymount Chain Bridge 5 m run+ swim
17 10 m run CF+5 m run Track Workout CF CF+10 m pace LSD: 20|W & OD Out-n-Back 5 m run+ swim
18 8 m run CF+5 m run Track Workout CF CF+4 m pace LSD: 14|Ross Drive 5 m run+ swim
19 6 m run CF+4 m run Track Workout CF CF+4 m run LSD: 10|MCM Taper Route 4 m run+ swim
20 4 x 400 2 m run rest rest 2 m run 1 mi run 26.2 race pace

Or, in Google calendar format

[googleapps domain=”www” dir=”calendar/hosted/theboohers.org/embed” query=”src=theboohers.org_o0027jm7d81mj2ma339qk5mbhk%40group.calendar.google.com&ctz=America/New_York” width=”800″ height=”600″ /]

By 0 Comments

Convert avi’s, mkv etc to the iphone

Handbrake is amazing with active development and a great community. When I wanted to transfer a bunch of lectures to iphone/ipod touch format, I faced the time-consuming challenge of converting a hundred files using the Handbrake gui and figuring out the complicated set of options for HandbrakeCLI — their nightly build standalone.

So, here is what I did:

I assume you have the Handbrake gui installed, but not the binary. Go to Handbrake http://build.handbrake.fr/ and get the latest CLI executable for your operating system and put it in a good place, preferably in your path.

Next, you want to make use of the excellent presets in the gui of Handbrake, use the ruby file manicure.rb to get the settings you need. (http://trac.handbrake.fr/browser/trunk/scripts/manicure.rb). Make sure you have the plist gem installed and then you can run.


bonhoffer Tim$ ./manicure.rb -rH > presets

In presets, I found the code I wanted, which I then put in ruby code of my own:


#! /usr/bin/ruby

# handbrake converts all files automatically for the ipod, nice

Dir.glob("*.avi").each do |filename|
  correct_fn = filename.sub(/s/,'-') # let's take out spaces from our filenames
  File.rename(filename,correct_fn)
  # change the output file name to an mp4
  new_file_name = correct_fn.sub(/avi$/,'mp4')
  # now it is time for business
  system("HandBrakeCLI -i #{correct_fn} -o #{new_file_name} -e x264 -q 20.0 -a 1 -E faac -B 128 -6 dpl2 -R 48 -D 0.0 -f mp4 -X 480 -m -x cabac=0:ref=2:me=umh:bframes=0:subme=6:8x8dct=0:trellis=0")
end

It is running in the background right now. No silly, clickity, click, click of the gui.

By 0 Comments

Hard drive mirroring With Unison and the DNS-323

My requirements:
– All files on my laptop will be on my DNS-323 Network attached storage device
– Any changes made to the shared drive on the DNS-323 will propagate to my Mac
– This happens securely and safely

The problem is that there are scant resources online to do this. (Isn’t this done frequently?) I am worried that there is a better implementation for this, but it is the best way forward.

I did find “this link”:http://www.edsalisbury.net/linux/how-to-set-up-a-two-way-mirror-between-mac-os-x-and-ubuntu-linux/, “this one”:http://www.macgeekery.com/gspot/2006-07/complete_bi_directional_home_sync_and_backup_with_unison and “this”:http://www.payne.org/index.php/Mac_OS_X_File_Sync. But they didn’t really answer my question on how to get everything working with a DNS-323.

Previously, I hacked my DNS-323 to give me ssh access as well as installed the ipkg manager to install files. However, it seems no-one has installed unison on the DNS-323 before.

I am currently working on this and trying to figure out if I need to install unison on the DNS-323 or not. I am also worried that no-one else seems to be doing this. I generally arrive late to the party on this stuff, what are other folks doing for this?

By 0 Comments

Where Men Win Glory: The Odyssey of Pat Tillman

Jon Krakauer uses 416 pages to make the audacious claim that he has found the Nietzschen Uebermensch in “Pat Tillman”:http://en.wikipedia.org/wiki/Pat_Tillman — and thus feeds the roaring literary fire of condemnation for the Bush administration in particular and religious conservatives in general. In a facile and sloppy argument that makes liberal use of argument by anecdote, he goes out of his way to package and sell his vision of the ideal man. This provides the opportunity for contrast against his straw-man of the modern religious conservative who is (here we go again) an unenlightened coward, motivated only by power and control of the weak. Here is Pat Tillman as the literary device — constructed and packaged to advance the consummate liberal ideal: an Emerson reading gay rights advocate who used his prodigious strength and pugnacity only to defend the weak, all while scorning any faith that claims it knows anything with certainty.

Krakauer restricts his moral influences to enlightenment skeptics or pre-christian classical writers and with his moral canon thus defined, he uses ample references to the likes of the Iliad and Nietzsche to supply a ready alternative basis of morality. Beyond presenting a system of morality unadulterated by conservative principles, he goes out of his way to denigrate the integrity of conservatives. It didn’t take long to think I was reading Christopher Hitchens or Sam Harris while I really was just interested in nothing more than the subtitle: “The legacy of Pat Tillman”. It is clear why his chose his actual title as a direct lift from the Iliad. His point? Men win glory when they transcend conservative values. Thus a story which initially appears to be about a man’s story becomes an apologetic for one of the most important agendas of modern liberalism: defining a sense of morality apart from religion and tradition.

So every page is then devoted to either exalting our postmodern hero, Pat, or casting aspersions on conservatives. Mr Tillman is presented as a modern man who combines love of life, strength, wisdom, sensitivity, morality, liberal use of the F-bomb, all surrounded by an acerbic scorn for religion and political conservatives. And thus we have the central irony of this book: while Jon Krakauer’s claim is that conservatives used Pat as a pawn to seize greater power and control, Pat is yet used once again — this time he is an the icon of liberalism. Jon Krakauer in expositing Tillman has repeated the path of prior liberals such as Adolf Harnack who tried to redefine Christ as similar to themselves. Father George Tyrell (a Catholic modernist) described this interaction well: “The Christ that what Harnack sees, looking back through nineteen centuries of Catholic darkness, is only the reflection of a Liberal Protestant face, seen at the bottom of a deep well.”

So now we have Jon Krakauer/Pat Tillman fused into the ideal of a modern man unencumbered by conservatism and belief. And with this icon, Jon Krakauer tells a captivating story. On one level it is brilliant to use someone who was the symbol of strength and sacrifice (virtues conservatives have unjustly claimed as unique to them) to pillory conservatives. That Jon Krakauer is a master storyteller is beyond dispute. He has crafted a story involving a set of elements perfectly tuned to sell to the reading masses: elite athleticism, culture wars, the Iliad, Nietzsche, heroism, all with generous potshots against George Bush and Donald Rumsfeld. So Krakauer becomes another who rides the zeitgeist and ends up defining a component of it by offering Pat Tillman as the consummate example of liberal ethics. Clearly, this book will sell, but I’ll speculate that Krakauer’s ambitions are much higher than profit here. For he has written a philosophy book, not a biography. Seen through this lens, I would recommend this book to others who wish to understand the chorus of new atheists: goodness without God is not just possible, but even better than goodness adulterated by the varieties of organized religion.

With his claim understood, how strong is his case? Like William Gladstone, Krakauer is attracted to the morality of the Iliad, which preaches that moderation in all things is the most important component of morality. Nietzsche as well presents an appeal to strength, and along with Ayn Rand — touches something deep within me, that calls me to be braver and stronger — by MYSELF. But here is where personal experience comes into the picture, and I get to present some anecdotes of my own. Trying to succeed on my own strength only leaves me more bitter and selfish. Perhaps Krakauer can claim this is because I’m internally weak, or perhaps weakened by my faith. But personal strength and courage for me have only been the direct result of my knowledge of my weakness and depending on an external source for strength. Thus I present a different ideal of the greatest man: one who lays down his life for his friends — who spends his time not making himself a better human, but emptying themselves for the sake of others — and doing so not for some vague sense of justice, but for tangible reward in heaven and the joy of thankful worship.

Beyond advancing his philosophy with Pat Tillman as a prop, Krakauer went out of his way to provide talking points for the Democratic party establishment. It almost felt like contrived product placements seen in movies. For example, John Krakauer went out of his way to spend several pages on the 2000 presidential election, with the final claim that Bush, through the interference of conservatives on the Supreme Court, was unlawfully elected president. This all despite the findings of subsequent in-depth recounts. I bring this forward, because it is one thing to criticize an administration, but another to espouse the political talking points that come from either side of the aisle.

I also don’t share Krakauer’s cynicism that US leaders engage in wars simply to increase their own power. The little I know about how national security doctrine is put together in this country is that it’s a messy business. For better or worse, one or two individuals do not yield enough power to be responsible for the entire direction of the nation. The coterie of individuals considered responsible for everything that goes wrong in Iraq and Afghanistan is basically distilled down to the usual suspects. To me, this is such a tired, simplistic, message that somehow continues to sell books to an increasingly gullible American public who thinks their cynicism about the Bush administration is a panacea for their very Naivete.

Thus I found Krakauer’s argumentation style to be anecdotal and somewhat insulting. He makes tenuous connections like the fact that helicopter was not available on a particular day to be used as an argument that Iraq received too many resources to the detriment of operations in Afghanistan. Military operations planning and allocation of assets is a very complicated process that many factors can influence. Speculating on potential correlation and then building an argument for causation is the most freshmen of errors. (Can I also note that the B-1 is not a stealth bomber. Did he have anyone with Air Force knowledge fact check his book?)

If I set aside the plot to denigrate my faith and political beliefs, there is much I found intriguing about this book. Pat Tillman and I grew up at the same time in a similar culture. We both joined the military and perhaps have felt a similar scorn for its mediocrity. There is no doubt that he experienced the associated dissonance that comes from an ideal of a warrior as “guardian of Plato’s ideal state”:http://classics.mit.edu/Plato/republic.8.vii.html contrasted with the modern reality of an all volunteer force — infected by financially motivated bottom feeders and the soul-stagnating bureaucracy of modern big government. So while Krakauer would cast me closer to the book’s arch villain, the evangelical LTC Ralph Kauzlarich, than his exalted Tillman, I think Pat and I might have had a good bit to talk about. This is perhaps why I have such strong feelings about this book: there is no doubt that Pat Tillman has once again been used.

By 0 Comments

House Data, Telecom and Audio Plan

Under Construction

Chrissy and I listen to a lot of audio books and when we’re not doing that we like to have classical background piano music playing. Lately, we’ve been amazed by how nice it is to have our Bose wave radio playing soft and quiet Christian music using Pandora which is being streamed from Chrissy’s iPhone. Since we’re putting some insulation in our basement and about to seal off the ceiling, we decided its time to at least wire our basement and consequently the rest of the house for whole house audio.

Fortunately, there a lot of great resources on the Internet for this. Crutchfield has some good tutorials, but they seem to be priced for much different market — namely those willing to spend a lot of money on audio. While we like technology, I’ve never quite considered myself an audiophile, at least at this stage to not have interest in buying the highest quality speakers in optimizing our sound throughout the home, but rather want to have good quality sound so we can listen to an audio book while we clean, or perhaps relaxing piano music while we play with our kids.

The three most helpful resources in determining our whole house audio plan were:

Crutchfield. Even though their prices are quite high, they have very good online tutorials. See their learning center. They also have a good selection of equipment, most focusing on custom multizone high fidelity systems.

Slashdot. This one really piqued my interest. Here you can find a great discussion from hackers perspective of how to build a whole house audio system doing everything from taking components of car stereos to modifying 70s gear.

I myself feel straddled between these two options. The thought of hacking a bunch of hardware together is those amazingly appealing, but nonetheless would require too much time. However, I called Crutchfield and asked them for custom system design. As expected, the price premium was too high. Fortunately, the spectrum between a hacked set of old hardware and a custom solution can be found from a number of other websites. The best tutorial that has a lot of DIY insights as well as links to equipment is found at the home tech website. Not only do they have good prices, but they also cover the history and a big picture view of whole house audio systems. There, they get down into details such as pre-wiring and installing a whole house audio system. hometech article

So after the research from sites such as those described above it looked like a system that was able to send audio data through category five cabling (what most folks know as ethernet cables) would provide the best option for the major zones around the house were simply looking for background music or mono audio signals. Not only could you buy the complete kit with nice key pads for under $600, but it seem like the level of fidelity and ease of installation were just right. Moreover, I’ll now have cat 5 wiring going to all controls throughout my house, which provides what I really want, an opportunity for progressive growth for our system, and preserves the potential for completely integrated home automation for the future.

From designing online businesses web presences,

what are our goals?

  • provide growth path
  • listen to an audio book throughout the house as we clean/work
  • listen to background music for party (indoor and out)
  • listen to music as our children play

what is our budget (equipment)?

we need an audio plan for our house

Design

Speaker Calculation

OSD Audio ICE620TT 6.5-inch 125-Watt Polypropylene Dual Voice Coil In-Ceiling Stereo Speaker, Single

  • Woofer: 6 1/2-Inch polypropylene woofer with dual voice coil
  • Tweeter: Dual (2) 5/8-Inch center mounted tweeters
  • Power handling: 125W, Impedance: 8 / 16 Ohms
  • Frequency Response 40Hz – 22kHz, Sensitivity 89db
  • Cut-Out Diameter: 7 3/4-Inch, Unit Dimension: 9-Inch diameter x 3 1/4-Inch deep
  • Shipping: This item does not ship outside the U.S

In order to power these, I’m thinking this OSD link, but much cheaper under the Dayton brand at Amazon. You can find the manual here.

So in order to distribute them, I’m thinking about this.

Downstairs audio and data plan

Upstairs audio and data plan

you can figure out where to place speakers here

By 0 Comments

Open: An Autobiography by Andre Agassi and J. R. Moehringer

Some books I read are entertaining. Some books give me a new perspective on certain facets of the world. Some books give me the ability to brag to others that I read them. Open did all of this. (Except that bragging bit. By the way, did I mention I am reading Thomas Khun too?) In any case, I expected an entertaining read, but I was surprised to find some bigger questions than the stated subject matter: tennis and its most (im)famous protagonist. A book that entertained and taught me a bit about tennis would’ve still been great. Tennis has played a huge role in my life: it taught me self-confidence; taught me how to win. It is also the means of some of the blessings I hold most dear: a loving father who taught me the sport, close friendship, and opportunity to work hard at something and get better at it. When I remember having fun as a kid, I remember tennis.

So naturally, my experience was very different than Agassi’s. He was forced into tennis — without the loving father, the friends, the fun. The chorus of the book was “I hate tennis”. It is like he woke up surrounded by a tennis matrix. Fear held him in a like a prisoner who is kept in the middle of the desert with the knowledge that his escape would only lead to certain death. So while he was given much, and developed into a super athlete, his mental state was left underdeveloped, indeed very fragile, by his pathological father.

It is difficult for me to consider big questions from books off of the New York Times bestseller list because I’m aware of the motivations of publishers, the role of the ghostwriter, and the economic gains that everyone can enjoy from a bestseller. This forced me to ask myself throughout the book: how authentic really is this guy? While writing a sports autobiography like this is certainly fresh and inviting, it is also a great potential marketing strategy. Not only that, but as someone who desperately wanted the opportunities that Andre was given: the chance this practice tennis full time at the Bollettieri Academy, all the equipment, travel and advantages that a young aspiring tennis player could wish for, I found myself both jealous and disdainful of his lack of thankfulness for his once in a billion opportunity to become the best tennis player in the world.

However, despite its potential flaws, I have to say that I like the book for three reasons. The first two are pretty simple: it brought back and augmented memories foundational to my childhood, and the book is very well written. Beyond this, the book was something more than entertaining. Andre was not just cataloging how he became a great tennis player or what the life as a great tennis player was like, he continually asked and tried to answer the question: what is success and what is life’s overarching purpose?

While he never asked these questions overtly, they were made unavoidable by the narrative: how could someone who just finished second in a tournament like the French Open be launched into a deep and abiding depression by his “devastating loss”? He was the number two tennis player in the world! Few in history have had that honor. Simply losing one match was enough to crush him. Was this because of the fragile emotional state by his one-sided upbringing, or does this speak to something deeper that afflicts us all?

My initial reaction to this was that Andre was a first-class whiner. Why couldn’t he be happy with taking home a $50,000 check instead of taking home $100,000 check or whatever tennis players get from these big tournaments? However, as the book went on I was forced to think through and try to understand how similar this struggle is for all of us.

I was reminded of discussions with senior leaders in the US military who mentioned colleagues of theirs who retired bitter and weary, crippled from their lack of ability to get that fourth star. Or how about political campaigns? The scale of loss and massive defeat that surrounds the losing nominee (and their millions of dollars spent) is staggering. And, no matter what our endeavor, no one always wins (excepting, perhaps, John Maynard Keynes). Perhaps the first of Buddha’s four Noble truths is unavoidable:

bq. To live means to suffer, because the human nature is not perfect and neither is the world we live in. During our lifetime, we inevitably have to endure physical suffering such as pain, sickness, injury, tiredness, old age, and eventually death; and we have to endure psychological suffering like sadness, fear, frustration, disappointment, and depression. Although there are different degrees of suffering and there are also positive experiences in life that we perceive as the opposite of suffering, such as ease, comfort and happiness, life in its totality is imperfect and incomplete, because our world is subject to impermanence. This means we are never able to keep permanently what we strive for, and just as happy moments pass by, we ourselves and our loved ones will pass away one day, too.

Without forgiveness and the love it enables, I think this does describe the surrounding tissue of a life in this world: as surely as gravity pulls a stone to earth our activities will encounter suffering, but there is hope! And here I found this book most wanting — a great description of the problem, with a sorry excuse for an answer. While I was glad to be faced with the question of what will I center my life around? What do I consider success? I was not satisfied with: “give”. He was three letters short of the answer — forgive and be forgiven. While giving to others certainly is a source of the deepest joy I have known, can it be done without experiencing forgiveness personally? And without this forgiveness, is happiness truly attainable even if one finds the Steffi Graf of their dreams?

As the parent of young children, it is interesting to follow what pulls them through each day, each week, each month. They are always excited about something. Sometimes it’s their birthday, or school the next day, their smile always has a very visible reason behind it. Throughout the book I found myself wanting Andre to smile. Not laugh for joy from the elation of finally winning a grand slam or defeating Boris Becker after the summer of revenge, but to smile because he deeply enjoyed something in and of itself. A quote from Screwtape letters is foremost in my mind here:

bq. On your own showing you first of all allowed the patient to read a book he really enjoyed, because he enjoyed it and not in order to make clever remarks about it to his new friends. In the second place, you allowed him to walk down to the old mill and have tea there””a walk through country he really likes, and taken alone. In other words you allowed him two real positive Pleasures. Were you so ignorant as not to see the danger of this? The characteristic of Pains and Pleasures is that they are unmistakably real, and therefore, as far as they go, give the man who feels them a touchstone of reality. (Letter XIII)

All this forced me to ask myself why I smile and when I smile. I smile when I think about finishing various projects at work or about having a good workout. I joyfully look forward to moment when I can sit down and read a book, or write this a review like this. But on reflection reasons why I smile are much less important than reasons why I cry. I have cried while holding my daughter in my arms while I think about her future. I’m overcome with feeling of being blessed — so excited to have someone to give to, to love. Excited to do so from a position of being forgiven and therefore from a position that is able to forgive all others. As tears carefully creep down my cheeks I feel, in a moment, at total peace, and in that moment I worship my creator.

But Andre, he never let us into those things that made him smile (except for his trip to South Africa to meet Nelson Mandela) and, of course, Steffi Graf. In Open, two criteria were necessary for an event to be included in the book: it was either something that brought him great disappointment or something that created national news, which most often had something unknown and disappointing behind it — often a bit of his angst that he was hiding from the cameras. True, the end is uplifting and perhaps there was a smile on his face as he traded volleys with Stephanie. The story just felt incomplete without forgiveness. We were left with the fear of the Dragon but not the forgiveness to his father. We were left with the legacy of angry sportswriters without a cathartic sense that he understood their need to play to the crowd just like he did. We have his broken relationship with Brooke Shields, not forgiven, just replaced by what he claims is his ultimate fulfilling relationship with Steffi Graf, a final upgrade perhaps.

The other major theme that Open highlighted was the messy nature of reality. I seem to have a Panglossian notion of how two elite tennis players come together on a court — imagining a degree of precision and perfect preparation commiserate with the high-stakes game they’re playing. It’s funny how events in my life which seems so clean-cut and simple were messy in their execution. Just like Pete Sampras crippled by cramps before a stunning victory over Andre I often find myself needing to overcome insurmountable odds to make an event happen. Why isn’t anything easy? Is this a case for everyone? Shakespeare certainly thought so.

Enough of the big picture questions, Open still contained a great deal of information on how the world works, and for that alone it’s a highly recommended read. So many events that capture the world’s attention were explained from a fascinating inner perspective — good reminders to us all that all is not as it seems. It was fascinating to watch Andre catalog his rise to fame and its accompanying transformation on what his life was like. Though they had so much in common, clearly, his rise was about as different as possible from that of Brooke Shields, who essentially grew up being famous. It seemed to be his Wimbledon victory which took him from famous in tennis circles to to a household name and the story started to take place in places like Kevin Costner’s yacht instead of on a tennis court in Florida.

So highly recommend: whether you’re looking for a chance to peek in the lives of the rich and famous, a reinterpretation of one of Tennis’ most memorable stars, a chance to understand what the American dream really is, or even looking to ponder some of life’s biggest questions. Whatever their decision, Agassi and J. R. Moehringer decided to make this a different kind of sports autobiography, one that opened a life and provides us ample opportunities to see ourselves in the mirror and ask whatever questions we see when that happens.

By 0 Comments