Saturday, 1 June 2013

Let's Deploy!

My partner, Ben, is putting together a series of articles for TechRepublic on choosing between Sinatra, Padrino and Rails.
To help him determine the management pros and cons of each system, I decided to do a series of posts about the end to end processes with each.
I started with a simple application that addresses some of the common issues faced in the real world.
The simple application accepts requests in a RESTful fashion for IPv4 addresses and returns the associated country in JSON or XML format.
To make the decisions more interesting, I have added the following versions of each system:

  • ORMs: DataMapper, ActiveRecord and Sequel.
  • DBs:  Development: Sqlite and MySQL, Production: MySQL and PostgreSQL
  • Deployment Targets: Hosted server, Heroku and EngineYard
  • Deployment Systems: Capistrano and Vlad

Topics covered in development and production include:

  • Access logging (Apache style)
  • Execution logging (Log4j style)
  • Migrations
  • Initial data seeding
  • Simple authorization

Source control will be managed using Git.
All code will use Ruby2.

So over the next few weeks I will be posting these articles with all source code.
All the source code will be made available via my GitHub repo.

After that I will consider doing the same for JRuby, Java, Groovy and Grails onto Jetty, Tomcat and Resin.
If I'm feeling perverse enough, I may do the same for PERL and maybe even PHP5 under Quercus (PHP on the JVM)

It's all a learning experience!

Enjoy.

P.S. If anyone has any suggested additions to the above please leave a comment.

Tuesday, 21 May 2013

gem install mysql2 missing mysql.h on OS X

Ok. The problem surfaced after I needed Ben to help on a site I was building.
He didn't have ruby or mysql installed on his laptop.
The site was built using Ruby-2.0.0-p195 and Rails-4.0.0-rc1.
That, in itself, isn't the issue.

We went and installed the x86_64 version of MySQL 5.6 on his machine.
This sets up in /usr/local/mysql.
Got a bit annoyed as the install doesn't have any StartupItems so start/stop is manual.
Ce la vie.

I now installed Ruby-2.0.0-p195 with no dramas.

The site code was loaded up into a folder and "bundle install" was run.
That's when the fun started.
The mysql2 gem wouldn't compile.
You get something like this:

This could take a while...
ERROR:  Error installing mysql2:
 ERROR: Failed to build gem native extension.

    /usr/local/rvm/rubies/ruby-2.0.0-p195/bin/ruby extconf.rb
checking for rb_thread_blocking_region()... yes
checking for rb_wait_for_single_fd()... yes
checking for mysql.h... no
checking for mysql/mysql.h... no
-----
mysql.h is missing.  please check your installation of mysql and try again.
-----
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
 --with-opt-dir
 --with-opt-include
 --without-opt-include=${opt-dir}/include
 --with-opt-lib
 --without-opt-lib=${opt-dir}/lib
 --with-make-prog
 --without-make-prog
 --srcdir=.
 --curdir
 --ruby=/usr/local/rvm/rubies/ruby-2.0.0-p195/bin/ruby
 --with-mysql-config
 --without-mysql-config


Gem files will remain installed in /usr/local/rvm/gems/ruby-2.0.0-p195/gems/mysql2-0.3.11 for inspection.
Results logged to /usr/local/rvm/gems/ruby-2.0.0-p195/gems/mysql2-0.3.11/ext/mysql2/gem_make.out

Wait. What? The mysql.h does exist (and is valid) in the /usr/local/mysql/include folder.
I, of course, realised that I had not set the --with-mysql-include folder and tried that along with some others.
Nope.
No luck.

I did some googling and the options where confusing and ultimately useless.
The most common options are:
  • Install mysql server using homebrew. Wait. What?
  • Setting env ARCHFLAGS="-arch x86_64" before running gem.
  • Using bundle to create a .bundle/config file.
  • Including every --with-mysql option available on the gem install.
I didn't reinstall mysql as that seemed like silly talk.
I did try the --with-mysql-config=/usr/local/mysql/bin/mysql_config option with no luck.
I had this tickling sensation in my brain that indicated that I was buggerising around in the wrong 'mode' and that I was missing something gob smackingly obvious.
But I persevered.

I tested the /usr/local/mysql/bin/mysql_config program and it's output was like this:

Usage: /usr/local/mysql/bin/mysql_config [OPTIONS]
Options:
        --cflags         [-I/usr/local/mysql/include  -Wno-unused-private-field -Os -g -fno-strict-aliasing -arch x86_64]
        --cxxflags       [-I/usr/local/mysql/include  -Wno-unused-private-field -Os -g -fno-strict-aliasing -arch x86_64]
        --include        [-I/usr/local/mysql/include]
        --libs           [-L/usr/local/mysql/lib -lmysqlclient]
        --libs_r         [-L/usr/local/mysql/lib -lmysqlclient_r]
        --plugindir      [/usr/local/mysql/lib/plugin]
        --socket         [/tmp/mysql.sock]
        --port           [0]
        --version        [5.6.11]
        --libmysqld-libs [-L/usr/local/mysql/lib -lmysqld]
        --variable=VAR   VAR is one of:
                pkgincludedir [/usr/local/mysql/include]
                pkglibdir     [/usr/local/mysql/lib]
                plugindir     [/usr/local/mysql/lib/plugin]

So I did some digging around in the gems folder and under the ext/mysql2 folder there was the mkmf.log file.
As I was stuffed up with flu, surrounded by used tissues and it was 3am it took me a little while to see the issue.
Then I saw it:

cc1: error: unrecognized command line option "-Wno-null-conversion"

It's cc1.
It's failing because it has been provided with a -W gcc option that isn't supported by the compiler.
I tried without luck to get "gem" and the "extconf.rb" to use the ENV['CC'] variable.
So using the gcc-4.2 compiler was the only option.
I checked the compiler version:

gcc -v
Using built-in specs.
Target: i686-apple-darwin11
Configured with: /private/var/tmp/llvmgcc42/llvmgcc42-2335.15~25/src/configure --disable-checking --enable-werror --prefix=/Developer/usr/llvm-gcc-4.2 --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-prefix=llvm- --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin11 --enable-llvm=/private/var/tmp/llvmgcc42/llvmgcc42-2335.15~25/dst-llvmCore/Developer/usr/local --program-prefix=i686-apple-darwin11- --host=x86_64-apple-darwin11 --target=i686-apple-darwin11 --with-gxx-include-dir=/usr/include/c++/4.2.1
Thread model: posix
gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)

Then it struck me that I was an idiot.
I edited the /usr/local/mysql/bin/mysql_config file and removed the offending -W compiler options.
(It's around line 120 or so)
The original looks like this:

cflags="-I$pkgincludedir  -Wall -Wno-null-conversion -Wno-unused-private-field -Os -g -fno-strict-aliasing -DDBUG_OFF -arch x86_64 " #note: end space!
cxxflags="-I$pkgincludedir  -Wall -Wno-null-conversion -Wno-unused-private-field -Os -g -fno-strict-aliasing -DDBUG_OFF -arch x86_64 " #note: end space!

And the changed version:

cflags="-I$pkgincludedir  -Wall -Os -g -fno-strict-aliasing -DDBUG_OFF -arch x86_64 " #note: end space!
cxxflags="-I$pkgincludedir  -Wall -Os -g -fno-strict-aliasing -DDBUG_OFF -arch x86_64 " #note: end space!

Now the install works just peachy.

Oh. One last point.
I had to link the dylib to /usr/local/lib:

ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib











Tuesday, 14 May 2013

Benji and Jesse are off to find their forever homes


Ok. Just took back the latest foster cats to the Animal Welfare League of Queensland.
You can see the fostering site at: http://www.facebook.com/AWLQLD or the main site at http://www.awlqld.com.au/

That makes 12 so far! 4 cats and 8 kittens.
All except the last two have found forever homes.
For cat lovers I have included pictures below.
Or you could just go to my Facebook page to see the album...

We picked these two up from the AWLQ  as they had been stuck in a cage for several weeks with cat flu. That's not cruelty BTW, but rather the foster centre had simply run out of space!

The Tabby (Benji) is a total companion cat and for the first week followed me around everywhere wanting head butts and cuddles.
After he got over the flu his 'instincts' took over and he was constantly trying to 'get it on' with me.
Now he's back at the fostering centre, they'll give him the snip and he'll just be a fantastic cat with no requirement to h__p his new mom. :-)
He would make a very welcome addition to a family with a large house as he likes to run around getting involved in everyones business.

 

 


















Jesse below is a year old and is a huge ball of fluff with a small cat embedded in it.
She's a very private cat but does like to sleep a lot. I suspect that an older couple would find her just peachy.

 

Tuesday, 30 April 2013

Bible Thumpers: Y U No Check Your Pamphlets?

Got one of these in the letter box on the weekend:


Can you see it? Oh. It's a bit small. Here's a close up:


Wait. What? Oh. So that's why I could never see the kingdom. It's over there, beyond the sparkley fountains of milk and honey in... in... HAD.

Seriously? It's the primary quote on the pamphlet for Cthulhus name.
The thing that grabs peoples attention.
And you got it wrong?


I did try google maps by the way. Gave up after a bit because it was boring. And stupid.
Mainly because of the semi-useless word "HAD."
The results where quite humorous and gave results focused on Israeli restaurants in:

Ash Sharqiyah, Oman
A possible I suppose.

Al Anbar, Iraq
Hmm. Seems to stretch the idea a bit don't you think?

And surprisingly:

Casper, WY, United States

Yeah. Real place. Loads of Israeli restaurants there according to Google.
Although I would hazard a guess and say that this kingdom is not likely to be at the Ramada Plaza Riverside Hotel and Convention Center unless they prefer the decor.
And the big question is, why Israeli?
I'm babbling.

Monday, 22 April 2013

Displaying a summary of Apache2 access logs by country using Ruby

Recently I had a case where a site was being hammered by script kiddies.
Nothing compromised I might add.
First off I limited access to the site by country (Australia in this case).
Note I wasn't blocking countries, but blocking all countries except Australia.

Then I wanted to get a summary of the Apache2 access log to see where these annoying little herberts where from.
An Apache2 access log entry looks something like this:


180.76.5.62 - - [22/Apr/2013:12:19:46 +1000] "GET /index.php?title=User:Coombayah HTTP/1.1" 403 409 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"


Now where was that access from?
So I knocked up a *very* simple ruby script to get that data.
The code below is 100% inelegant.
It's not meant to be elegant.
It's meant to illustrate the point.
A 'real' script would have much better coding and options for dates and ranges.

Output for today would look something like this:


2013-04-22 - 128
  119.63.193.131      2    0    2 2000667011 Japan
  119.63.193.132      2    0    2 2000667012 Japan
  123.125.71.112      2    0    2 2071807856 China
  123.125.71.74       2    0    2 2071807818 China
  176.31.9.218        4    0    4 2954824154 France
  192.80.187.162      4    0    4 3226516386 United States
  216.152.250.163     4    0    4 3633904291 United States
  216.152.250.187     4    0    4 3633904315 United States
  218.30.103.31       2    0    2 3659425567 China
  220.181.108.155     2    0    2 3702877339 China
  46.246.60.177       7    0    7  787889329 Sweden
  46.28.64.213       25    0   25  773603541 Ukraine
  66.249.74.135      10    0   10 1123633799 United States
  IPs with 1 access and failure: ["119.63.193.195", "119.63.193.196", "123.125.71.107", 
  "123.125.71.113", "123.125.71.69", "123.125.71.72", "123.125.71.75", "123.125.71.76", 
  "123.125.71.83", "123.125.71.91", "173.255.217.233", "180.76.5.10", "180.76.5.15", 
  "180.76.5.162", "180.76.5.62", "180.76.5.7", "180.76.6.227", "202.46.48.27", 
  "202.46.61.34", "220.181.108.143", "220.181.108.147", "220.181.108.148", 
  "220.181.108.158", "220.181.108.159", "220.181.108.161", "220.181.108.162", 
  "220.181.108.178", "220.181.108.181", "220.181.108.183", "220.181.108.186", 
  "42.98.185.25", "46.161.41.24", "78.46.250.165", "80.93.217.42", "85.216.108.89"]


As you can see, pretty basic.
The principal rows show the IP address, accesses, successes, failures, IP-Value and country.

First off you need a Ip-To-Country list.
In a prior post I mentioned getting zone range files.
So go to http://software77.net/geo-ip/ and get the full list.
The file will have a lot of useful comments at the top.
Make a copy of the file excluding the comments as IpToCountry.csv.
You should see a file roughly 126,000 lines long where each line looks something like this:


"16777216","16777471","apnic","1313020800","AU","AUS","Australia"


The fields are 'Address From', 'Address To', 'Registrar', 'Date Assigned', 'Country2', 'Country3', 'Country'.

And now the ruby code.

First the requires:


require 'date'
require 'csv'


Since we are using *BRUTE FORCE* and not bothering to be elegant, we simply read the csv file into an array of hashes.


printf "Loading Ip to Country map\n"
ip_to_country = []
CSV.foreach('IpToCountry.csv') do |row|
  ip_to_country << { :from => row[0].to_i, :to => row[1].to_i, :country => row[6] }
end


The first pass now reads the access log into a hash of hashes:


unique_days = {}
f = File.new('logs/access.log', 'r')
while (line = f.gets)
  ip,tmp,tmp,dt,offset,verb,url,http,rcode,sz,tmp,browser = line.split
  d = DateTime.strptime( "#{dt} #{offset}", "[%d/%b/%Y:%H:%M:%S %Z]")
  date = d.strftime("%Y-%m-%d")
  if ! unique_days.has_key? date
    unique_days[date] = { :ip => {}, :total => 0 }
  else
    if ! unique_days[date][:ip].has_key? ip
      unique_days[date][:ip][ip] = { :total => 0, :succeeded => 0, :failed => 0 }
    end
    unique_days[date][:ip][ip][:total] += 1
    if rcode == '200'
      unique_days[date][:ip][ip][:succeeded] += 1
    else
      unique_days[date][:ip][ip][:failed] += 1
    end
    unique_days[date][:total] += 1
  end
end
f.close


I have eschewed elegance here for brute force and clarity.

So. Now we have everything we need.
Now we traverse the data dumping the report out:


unique_days.each do |date,h|
  printf "#{date} - #{h[:total]}\n"
  only_one_failed = []
  h[:ip].sort.map do |k,data|
    octets = k.split('.')
    if data[:total] == 1 && data[:failed] == 1
      only_one_failed << k
      next
    end
    ip_value = (octets[0].to_i * 256 * 256 * 256) + (octets[1].to_i * 256 * 256) + (octets[2].to_i * 256) + (octets[3].to_i)
    country = 'Unknown'
    ip_to_country.each do |row|
      if ip_value >= row[:from] && ip_value <= row[:to]
        country = row[:country]
        break
      end
    end
    printf "  %-16s %4d %4d %4d %10d %s\n", k, data[:total], data[:succeeded], data[:failed], ip_value, country
  end
  printf "  IPs with 1 access and failure: #{only_one_failed}\n"
end


And you have your report.

Configuring an Apache2 instance to only allow access from a specific set of countries

Ok. This is pretty simple, but I'm documenting it here for myself.
The issue arose for me when one of our test servers started getting hammered by script kiddies from China and so on.
They didn't get in, but that wasn't the point.
The point was that the web site was for Australian users only.
Now to short circuit any comments I can say:
1) The solution had to work for several frameworks (Elgg, Joomla, etc)
2) I wasn't redirecting traffic to a special page
3) I wanted to black all access except Australian IPv4 ranges
I could have gone to MaxMind or its ilk, but that would make '1' problematic.

The first step was to locate a *correct* and *regularly updated* list of CIDR and range formatted IPv4 list.
I tried several including http://www.ipdeny.com/ipblocks/ with no luck.
Most have gaps in their lists because they use 'official' sources.
And ISP ranges are not always covered in those lists.

Eventually I found http://software77.net/geo-ip/
I downloaded the zone and CIDR lists for the countries I was interested in.
Then I modified the /etc/apache2/sites-available/my-site.something.com file to look like this:
<caveat>
Abbreviated for this post. :-)
</caveat>

<VirtualHost *:80>
  ServerAdmin     my.email@my.email.service.com
  ServerName      my-site.something.com
  ServerSignature Off
  DocumentRoot    /home/my-user/my-site/site
  LogLevel        warn
  ErrorLog        /home/my-user/my-site/logs/error.log
  CustomLog       /home/my-user/my-site/logs/access.log combined
  ScriptAlias     /cgi-bin/ /usr/lib/cgi-bin/
  <Directory /home/my-user/my-site/site/>
    Options       Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order         Deny,Allow
    # See .htaccess for country limiting
  </Directory>
</VirtualHost>

Then in the /home/my-user/my-site/site/.htaccess file I added the following to the bottom:

<Limit GET HEAD POST>
  Order Deny,Allow
  Deny from all
  # Report generated on Mon Apr 22 01:02:36 2013
  # by http://software77.net/geo-ip/
  # Report Type  : CIDR format
  # Country      : Australia
  # ISO 3166 CC  : ALPHA-2 AU; ALPHA-3 AUS
  # Registry     : APNIC
  # Records found: 6,160 BEFORE flattening (As they appear in the database)
  # Records      : 3,999 AFTER flattening (Adjoining CIDR blocks concatenated into single blocks where possible)
  Allow from 1.0.0.0/24
  Allow from 1.0.4.0/22
  ...elided for brevity...  
  Allow from 223.255.248.0/22
  Allow from 223.255.255.0/24
</Limit>

The block from under the 'Deny from all' to the end is the CIDR formatted file for a country.
You have to add the 'Allow from ' in front of the address, but if you use vi that's trivial.
If you want to allow another country access, then simply add the CIDR formatted file below the first.

Then I ran '/etc/init.d/apache2 reload' and all is well.

IMPORTANT NOTE: Things change. You have to check for additions and deletions on the CIDR files on a regular basis - say, once a month to ensure that you .htaccess file is up to date.

Some readers may have noticed that I suggest downloading the zone files as well.
This is to allow you to read your apache logs and see which country that access came from.
I will be posting another entry soon that shows how to do that in Ruby.

Sunday, 21 April 2013

I've decided to stop watching debates about the existence or nature of god for a while


We foster cats and catlings.
"Wait. What?" I hear you say.
Just give me a few moments to explain.
Over the last year Ben and I have been fostering sick cats, mothers with kittens and the like.
Loads of them.
And, like 2 year olds, you have to watch them like a hawk.
They are always being mischevious, investigative and learning about the world they are in.
Since they are often sick or just kittens, we have to keep them inside for their own safety.
So they learn about the world that is the our house.

You can't concentrate on much, read a book and so on.
Some things you can do includes watching TV.
And I've been watching debates about the existence and nature of god while keeping a watchful eye on the fuzz balls.
So while you listen to Dawkins, Krause, Lennox et al, you observe the cats, their habits, their kittie-language and so on.

One thing that leaps out is the solid block that is the limit to their intelligence.
They can jump on chairs without knowing that it is a chair.
They eat food from a mechanical food chain beyond their grasp.
They cathramorphise us as their parent cats.
They can't read.
They may watch TV sometimes but have no capability of understanding any of the massive technological edifice behind those flickering images.
They are limited.
I suspect that even with evolution in play they will never be able to:
Open a can of cat food;
Read a book;
Build a car or fly a jet fighter within any reasonable time span.

So when I see a bunch of atheists, theists and deists debating I get an image in my head.
And that image is a bunch of house cats, sitting on a carpet discussing the nature of Dog.

I'm not saying that the debates are pointless and shouldn't be done.
They should continue, but on the understanding that a hundred years from now they will be debating the same thing.

So I'm laying off the debates for a while.

For cat-lovers here are some photos of the latest bunch:


Sunday, 24 March 2013

There's a few things that are hard for average people to understand


I'm kinda in a state of physical limbo.
It's damn hard to explain.
Because most people have no conception of what it's like to be 'half-n-half'.
For example...
For years, no decades, until I was 40 my left knee would dislocate.
Roughly once a month or so.
I mean that bone down the back of your calf.
There'd be a sudden movement and I'd be on my back in agony.
Muscles frantically jamming solid trying to move an immovable object etc.
Pain washing over me like waves...
I'd have to...
and if you're squemish skip to the next paragraph...
I'd have to jam my wrist around the back of my knee and with the other arm yank hard on my ankle.
There'd be this enormous "THWACK" as the bone snapped back into position.
I'd then faint.
My mother would run into the kitchen to avoid this.

Anyway...
Thankfully I don't have that problem now.
I'm guessing because of HRT dropping muscle mass etc.
But the basic issue still persists.
And it's so hard to explain because very few people have any idea what to equate it to.
I have a left collar bone.
Oh. Yes. You do too. That thin one at the top of your chest.
Left side.
No.
Your other left.
Anyway I've broken it a couple of times.
But that's not the issue.
The 'ball' at the end that fits into your chest bone is 'gracile.'
The socket is 'robust'.
For the non-anatomically inclined that roughly means gracile=>female, robust=>male.
So it doesn't quite fit.
And occassionally pops out.
And people in an office setting are sometimes privy to me pushing my chest out and...
THWACK.
As I pop it back into place.
Sometimes it doesn't work.
Like today.
I've been walking around all day with a dislocated collar bone trying to snap it back into place.
Driving.
Lifting.
Caring for kittens.
Sweating with pain.
Wiping tears away because you just have to soldier on.
"Ship? Out of danger?"
Still haven't got it back into place.
Hot showers help.
Pain killers?
Nothing - not a dent.
Anyway.
You have no clue what I'm talking about.
Life sucks sometimes.

Wednesday, 27 February 2013

Interesting human dynamic going on in the supermarket today.


Dropped in to pick up some essentials.
Stopped by the "mixers" aisle.
And was gob smacked.
My smack was gobbed so completely I nearly fainted.
Two people had filled a trolley to over flowing with every single bottle of Tonic Water on the shelves.
I wasn't alone in looking like I'd just been hit in the face with a fish.
Quite a few people had storm clouds over their heads.

Now.
Interesting.
You put items in your cart.
Until you pay for them they still 'technically' belong to the supermarket.
So it should be perfectly ok to go up to someones cart and simply take things from it.
Except it's not.
Why not?

Because it breaks some deep inbuilt rule we have about social behaviour.
So a group of people watched the pair struggle to get their cart to the checkout.
The thunderous looks would have done Wotan and Thor proud.
And I thought: "Ah. So this is why Assault Rifles are illegal in Australia."

Disgusted with myself at not saying: "Oi? WTF? You pricks!" I turned away.
Especially since I actually intended to get some tonic water.

At the end of the aisle I noticed some "DIET" tonic water.
"Oh lord no," I thought, "I'm reduced to this."
But Ben noticed a 'normal' bottle at the back.
We pushed aside some bottles and to our amazement there were four bottles of "normal" tonic water.
I grabbed them.
And covered them with sliced cheese and milk bottles.

We made our way to the checkout.
Seriously you would need a machete to cut the air.
The hatred and anger flowing from the people around this tonic water pair was palpable.
Now I know that only a few of them were likely to actually be buying tonic water.
But these two were blissfully unaware of the hundreds of daggers and muttered oaths.
They went through, paid and left.
The air tasted of cordite.
I really felt like if anyone noticed my four bottles I'd be hung, drawn and quartered.
Or in the US, riddled with bullets.

But it got me thinking about Altruistic Punishment.
Google it.
Now.
Oh and Sociopaths.
Most examples apply to cyclists, but I think this fits.

And this is why I buy books from Pragmatic Programmers!


Monday: Ruby 2.0.0 announced and available via rvm.
Tuesday: Rails 4.0 beta1 announced.
Wednesday: I get an email from progprog saying this:

This is just to let you know that Agile Web Development with Rails (4th edition) (eBook) has recently been updated. You own an electronic version of this book, and so you'll be able to download this latest version. We have also uploaded it to your Dropbox

I glance up to the toolbar and see the dropbox icon whirring and 2mins later I'm reading it.

Frickin brilliant.

Thanks Dave and Andy and all your gophers!

Now it's up to me to upgrade my apps...